javascriptasync-awaitpromisetry-catch

[UnhandledPromiseRejection]: Why try...catch handle promise rejection if the last promise rejects, but throws an error for intermediate rejections?


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.

  1. Why does the try...catch seem to only work if the last promise rejects?
  2. How can I modify my code to correctly handle rejections for all promises without triggering unhandled promise rejections?

Solution

  • 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,

    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.