JS Promise resolve function not working (while reject function does) - javascript

I have a simple async function in my JavaScript code:
export default class DanceAnimationCanvas extends AnimationCanvas {
async initTimeline(): Promise<any> {
return new Promise((resolve, reject) => {
const app = this.app;
const timeline = new TimelineLite();
app.loader.add(this.imageSrc).load((loader, resources) => {
const mainImage = new PIXI.Sprite(resources[this.imageSrc].texture);
app.stage.addChild(mainImage);
timeline.to(mainImage, { rotation: 10, duration: 100 });
console.log('resolve');
resolve(timeline);
});
});
}
}
The message 'resolve' is logged to the console, so I am sure that the resolve function is called. However, this code does not work:
async getCanvas(): Promise<HTMLCanvasElement> {
if (!this.timeline) {
try {
this.timeline = await this.initTimeline();
}
catch (error) {
console.log({ error });
}
}
console.log('timeline', this.timeline);
return this.app.view;
}
This function never resolves (the HTMLCanvasElement is never returned). Also the catch function is not called, so error is not logged.
The weird thing is that it DOES work when I use the reject function instead of the resolve function (which is wrong, I know):
export default class DanceAnimationCanvas extends AnimationCanvas {
async initTimeline(): Promise<any> {
return new Promise((resolve, reject) => {
const app = this.app;
const timeline = new TimelineLite();
app.loader.add(this.imageSrc).load((loader, resources) => {
const mainImage = new PIXI.Sprite(resources[this.imageSrc].texture);
app.stage.addChild(mainImage);
timeline.to(mainImage, { rotation: 10, duration: 100 });
reject(timeline);
});
});
}
}
async getCanvas(): Promise<HTMLCanvasElement> {
if (!this.timeline) {
try {
this.timeline = await this.initTimeline();
}
catch (timeline) {
this.timeline = timeline;
}
}
console.log('timeline', this.timeline);
return this.app.view;
}
Everything now works as I would want it to, but off course this is not the right way to do it. I'm probably not seeing something, but I've been at this for an hour and just cannot see why this happens...

Related

Anyone know why this Javascript Async Function is failing 1% of the time?

Thought I had this little routine nailed that would load Javascript Audio asynchronously but it fails every great once in awhile. Any idea what the issue could be? It seems to be ok if I repeatedly hit refresh but if it runs for awhile and then I hit refresh the screen will go blank and I don't see the "Sounds async load complete." pop up in the F12 console.
export default class Chant {
static bundles = {};
constructor() {
if (this instanceof Chant) {
throw Error('Chant is a static class and cannot be instantiated.');
}
}
static addBundle(bundle, sources) {
this.bundles[bundle] = sources;
}
static async loadBundle(bundleKey) {
const audios = {};
const bundle = this.bundles[bundleKey];
for (const key in bundle) {
audios[key] = await this._loadAsync(bundle[key]);
}
delete this.bundles[bundleKey];
return audios;
}
static async _loadAsync(src) {
return new Promise((resolve, reject) => {
const audio = new Audio(src);
resolve(audio);
audio.onerror = () => { reject(`Invalid audio source: ${src}`); };
})
}
}
And it's called like this:
import Chant from '/js/modules/chant.mjs';
// global variables
let sounds;
async function loadAudioAsync() {
// sounds
Chant.addBundle('sounds', {
spinner: 'assets/sounds/spinner-0.ogg',
horn: 'assets/sounds/party-horn-0.ogg',
pop: 'assets/sounds/pop-0.ogg'
})
sounds = await Chant.loadBundle('sounds');
console.log('Sounds async load complete.');
}
async function init() {
await loadAudioAsync();
}
windows.onload = () => {
init();
}

can you await a function not defined as async but declared new Promise inside of it?

We have this in our code:
const sqlUpsertRecommendations = recommendationsArray => {
// eslint-disable-next-line no-new
new Promise(resolve => {
const insertStatus = {
....
};
if (!recommendationsArray.length > 0) {
resolve({
error: false,
results: insertStatus,
localMessage: 'recommendationsArray.length = 0',
});
}
insertStatus.arrayEmpty = false;
dbConfig.transaction(txn => {
for (let i = 0; i < recommendationsArray.length; i++) {
const {
id,
recommendation_attribute,
recommendation_value,
segment_id,
target_attribute,
target_value,
is_deleted,
updated_at,
name,
image,
border_color,
} = recommendationsArray[i];
const params = [
id,
recommendation_attribute,
recommendation_value,
segment_id,
target_attribute,
target_value,
is_deleted,
updated_at,
name,
image,
border_color,
];
try {
txn.executeSql(
upsertRecommendations,
params,
(_tx, _results) => {
resolve({ error: false, results: insertStatus });
},
error => {
crashlytics().recordError(error);
if (__DEV__) {
console.log('sqlUpsertRecommendations error:', error);
}
const { notInserted } = insertStatus;
insertStatus.inserted = notInserted + 1;
if (1 + i === recommendationsArray.length) {
resolve({ error: false, ...error, results: insertStatus });
}
},
);
} catch (e) {
crashlytics().recordError(e);
}
}
});
});
};
I am learning async await and people in our team are using it like
const testFunction = async () => {
....
await sqlUpsertRecommendations(parsedData)
But vscode is saying
'await' has no effect on the type of this expression.
I just need idea how to best approach and use this kind of code?
You need to actually return the new Promise, then you can await it. If you don't, you just start a Promise and then immediately return undefined, and you can't await undefined.
const sqlUpsertRecommendations = recommendationsArray => {
// eslint-disable-next-line no-new
return new Promise(resolve => {
// ... rest of code
async is really just syntactic sugar for creating and using Promises. An async function just returns a new Promise and that's what await cares about, so await will work if you have a normal function that returns a Promise.

Javascript Promise within Class

Fun with promises today ... I'm trying to figure out why I can't refer to this.promise to resolve it with another method, it's always null.
Any enlightenment is appreciated :)
export default class Example {
constructor() {
this.promise = null
}
test() {
this.promise = new Promise((resolve, reject) => {
this.something()
}).then(resolve => {
// resolved, do something else
}).catch(reject => {
console.log('reject', reject)
})
}
something() {
this.promise.resolve()
}
}
You're trying to use this.promise in something before it's value changes from null. Another way to do this is to pass the resolve as an argument:
class Example {
constructor() {
this.promise = null;
}
test() {
this.promise = new Promise((resolve, reject) => {
this.something(resolve);
}).then(result => {
console.log('resolve', result);
}).catch(error => {
console.log('reject', error);
});
}
something(resolve) {
resolve(1);
}
}
const example = new Example();
example.test();

How to resolve promises and catch an error

I am trying to user webgazer.js where my code basically checks to see whether the webgazer is initialized and when it is initialized it resolves a promise which dispatches an action. This works however if for example there is no webcam I need to throw an error. The error in my code never gets called.
Here is my code
export function detectJsCamera() {
return async(dispatch) => {
dispatch({type: types.JS_DETECTING_CAMERA});
try {
await detectCamera();
await dispatch({type: types.JS_CAMERA_DETECTED});
} catch (error) {
await dispatch({type: types.CAMERA_DETECTION_FAILED, error: error.message});
throw error;
// this.props.history.push('/setup/positioning')
};
}
}
const detectCamera = () => new Promise((resolve, reject) => {
const checkIfReady = () => {
if (webgazer.isReady()) {
resolve('success');
} else {
console.log('called')
setTimeout(checkIfReady, 100);
}
}
setTimeout(checkIfReady,100);
});
You will need to reject in order to throw an exception like below
const detectCamera = () => new Promise((resolve, reject) => {
const checkIfReady = () => {
if (webgazer.isReady()) {
resolve('success');
} else {
console.log('called');
reject("some error");
}
}
setTimeout(checkIfReady,100);
});
You need to call reject() in your detectCamera method when your webgazer is not initialised then it would be caught in your catch block in detectJsCamera method.

Is it safe to resolve a promise multiple times?

I have an i18n service in my application which contains the following code:
var i18nService = function() {
this.ensureLocaleIsLoaded = function() {
if( !this.existingPromise ) {
this.existingPromise = $q.defer();
var deferred = this.existingPromise;
var userLanguage = $( "body" ).data( "language" );
this.userLanguage = userLanguage;
console.log( "Loading locale '" + userLanguage + "' from server..." );
$http( { method:"get", url:"/i18n/" + userLanguage, cache:true } ).success( function( translations ) {
$rootScope.i18n = translations;
deferred.resolve( $rootScope.i18n );
} );
}
if( $rootScope.i18n ) {
this.existingPromise.resolve( $rootScope.i18n );
}
return this.existingPromise.promise;
};
The idea is that the user would call ensureLocaleIsLoaded and wait for the promise to be resolved. But given that the purpose of the function is to only ensure that the locale is loaded, it would be perfectly fine for the user to invoke it several times.
I'm currently just storing a single promise and resolve it if the user calls the function again after the locale has been successfully retrieved from the server.
From what I can tell, this is working as intended, but I'm wondering if this is a proper approach.
As I understand promises at present, this should be 100% fine. The only thing to understand is that once resolved (or rejected), that is it for a defered object - it is done.
If you call then(...) on its promise again, you immediately get the (first) resolved/rejected result.
Additional calls to resolve() will not have any effect.
Below is an executable snippet that covers those use cases:
var p = new Promise((resolve, reject) => {
resolve(1);
reject(2);
resolve(3);
});
p.then(x => console.log('resolved to ' + x))
.catch(x => console.log('never called ' + x));
p.then(x => console.log('one more ' + x));
p.then(x => console.log('two more ' + x));
p.then(x => console.log('three more ' + x));
I faced the same thing a while ago, indeed a promise can be only resolved once, another tries will do nothing (no error, no warning, no then invocation).
I decided to work it around like this:
getUsers(users => showThem(users));
getUsers(callback){
callback(getCachedUsers())
api.getUsers().then(users => callback(users))
}
just pass your function as a callback and invoke it as many times you wish! Hope that makes sense.
There s no clear way to resolve promises multiple times because since it's resolved it's done. The better approach here is to use observer-observable pattern for example i wrote following code that observes socket client event. You can extend this code to met your need
const evokeObjectMethodWithArgs = (methodName, args) => (src) => src[methodName].apply(null, args);
const hasMethodName = (name) => (target = {}) => typeof target[name] === 'function';
const Observable = function (fn) {
const subscribers = [];
this.subscribe = subscribers.push.bind(subscribers);
const observer = {
next: (...args) => subscribers.filter(hasMethodName('next')).forEach(evokeObjectMethodWithArgs('next', args))
};
setTimeout(() => {
try {
fn(observer);
} catch (e) {
subscribers.filter(hasMethodName('error')).forEach(evokeObjectMethodWithArgs('error', e));
}
});
};
const fromEvent = (target, eventName) => new Observable((obs) => target.on(eventName, obs.next));
fromEvent(client, 'document:save').subscribe({
async next(document, docName) {
await writeFilePromise(resolve(dataDir, `${docName}`), document);
client.emit('document:save', document);
}
});
If you need to change the return value of promise, simply return new value in then and chain next then/catch on it
var p1 = new Promise((resolve, reject) => { resolve(1) });
var p2 = p1.then(v => {
console.log("First then, value is", v);
return 2;
});
p2.then(v => {
console.log("Second then, value is", v);
});
You can write tests to confirm the behavior.
By running the following test you can conclude that
The resolve()/reject() call never throw error.
Once settled (rejected), the resolved value (rejected error) will be preserved
regardless of following resolve() or reject() calls.
You can also check my blog post for details.
/* eslint-disable prefer-promise-reject-errors */
const flipPromise = require('flip-promise').default
describe('promise', () => {
test('error catch with resolve', () => new Promise(async (rs, rj) => {
const getPromise = () => new Promise(resolve => {
try {
resolve()
} catch (err) {
rj('error caught in unexpected location')
}
})
try {
await getPromise()
throw new Error('error thrown out side')
} catch (e) {
rs('error caught in expected location')
}
}))
test('error catch with reject', () => new Promise(async (rs, rj) => {
const getPromise = () => new Promise((_resolve, reject) => {
try {
reject()
} catch (err) {
rj('error caught in unexpected location')
}
})
try {
await getPromise()
} catch (e) {
try {
throw new Error('error thrown out side')
} catch (e){
rs('error caught in expected location')
}
}
}))
test('await multiple times resolved promise', async () => {
const pr = Promise.resolve(1)
expect(await pr).toBe(1)
expect(await pr).toBe(1)
})
test('await multiple times rejected promise', async () => {
const pr = Promise.reject(1)
expect(await flipPromise(pr)).toBe(1)
expect(await flipPromise(pr)).toBe(1)
})
test('resolve multiple times', async () => {
const pr = new Promise(resolve => {
resolve(1)
resolve(2)
resolve(3)
})
expect(await pr).toBe(1)
})
test('resolve then reject', async () => {
const pr = new Promise((resolve, reject) => {
resolve(1)
resolve(2)
resolve(3)
reject(4)
})
expect(await pr).toBe(1)
})
test('reject multiple times', async () => {
const pr = new Promise((_resolve, reject) => {
reject(1)
reject(2)
reject(3)
})
expect(await flipPromise(pr)).toBe(1)
})
test('reject then resolve', async () => {
const pr = new Promise((resolve, reject) => {
reject(1)
reject(2)
reject(3)
resolve(4)
})
expect(await flipPromise(pr)).toBe(1)
})
test('constructor is not async', async () => {
let val
let val1
const pr = new Promise(resolve => {
val = 1
setTimeout(() => {
resolve()
val1 = 2
})
})
expect(val).toBe(1)
expect(val1).toBeUndefined()
await pr
expect(val).toBe(1)
expect(val1).toBe(2)
})
})
What you should do is put an ng-if on your main ng-outlet and show a loading spinner instead. Once your locale is loaded the you show the outlet and let the component hierarchy render. This way all of your application can assume that the locale is loaded and no checks are necessary.
No. It is not safe to resolve/reject promise multiple times. It is basically a bug, that is hard to catch, becasue it can be not always reproducible.
There is pattern that can be used to trace such issues in debug time. Great lecture on this topic: Ruben Bridgewater — Error handling: doing it right! (the part related to the question is around 40 min)
see github gist: reuse_promise.js
/*
reuse a promise for multiple resolve()s since promises only resolve once and then never again
*/
import React, { useEffect, useState } from 'react'
export default () => {
const [somePromise, setSomePromise] = useState(promiseCreator())
useEffect(() => {
somePromise.then(data => {
// do things here
setSomePromise(promiseCreator())
})
}, [somePromise])
}
const promiseCreator = () => {
return new Promise((resolve, reject) => {
// do things
resolve(/*data*/)
})
}

Categories