async function all(...params) {
let result = [];
for (let promise of params) {
try {
let res = await promise;
result.push(res);
} catch (e) {
result.push({
error: e
});
}
}
return result;
}
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(1);
}, 1000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 2000);
});
all(promise1, promise2, promise3).then(console.log);
I'm working with an async function in JavaScript that processes multiple promises using a try...catch block to handle any potential errors.
I have tried debugging and found that the try catch works for if the last promise rejects and throws error if any intermediate promise rejects.
The uncaught rejections
The issue is that the posted code waits consecutively for each promise in the argument list to settle before adding a catch handler to promises passed later in the argument list.
Using the posted code as an example,
await
effectively adds a pair of onfulfilled
and onrejected
handlers to a promise operand so that when it becomes settled, await
can return the fulfilled value or throw the rejection reason. So the awaited promise won't generate an "uncaught promise rejection" error because it has a rejection handler.
the first promise awaited in the synchronous for..of
loop is promise1
which takes 3 seconds to fulfill.
During those 3 seconds the await
operator returns control to the event loop, the timers for promise2
and promise3
expire and reject their promises - which without a catch handler are treated as uncaught rejections.
Solution
The solution is to add handlers to all of the promises before waiting for any of them, or alternatively don't use await
and add handlers that effectively implement the Promise static method that is being polyfilled.
Real Example
<disclaimer> this is code for a Promise.all
polyfill written in ES3 as part of promise experimentation. Provided FYI, usual conditions of "provided as is", "don't sue me", etc apply.</disclaimer>
function assertPromiseClass( oThis, method) {
if( oThis === Promise || oThis instanceof PromiseClass) {
return oThis;
}
if( (!oThis && bNode)|| (bNode && oThis === Global) ) { // Promise/A+ testing?
return Promise;
}
if( isFunction( oThis) && isFunction( oThis.resolve)
&& isObject( oThis.prototype) && isFunction( oThis.prototype.then) ) {
return oThis;
}
var msg = "Assert Promise."+method+" called on a Promise-like constructor failed.";
console.trace();
console.error( "this value: ", oThis);
throw new Error( msg);
}
Promise.all = function( arg) { // Promise.all
var array;
var C = assertPromiseClass( this, "all");
return new C( function(resolve, reject) {
if( !(array = from(arg))) {
reject( new TypeError("Promise.all unable to iterate over " + arg));
return;
}
var todo = array.length;
var done = false;
var results = [];
function slowResolve(i, data) {
if(!done) {
results[i] = data;
if( --todo == 0) {
done = true;
resolve( results);
results = null;
}
}
}
function fastFail( err) {
if( !done) {
done = true;
reject(err);
results = null;
}
}
if( array.length) {
each( array, function( promise, i) {
promise = C.resolve( promise);
bChain = false;
promise.then( function( data) { slowResolve(i, data)}, fastFail);
bChain = true;
});
}
else {
resolve( results);
}
});
};
The above functions are in an IIFE - Promise
is an argument, an bChain
is an optimization to prevent the polyfilled then
method from returning a promise.