1

NodeJS version 8.11 - The warning is "This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()."

Neither of these are true, which you can see by inspecting my test case:

const DEMONSTRATE_FAILURE = false

async function doPromises() {
    try {
        if (DEMONSTRATE_FAILURE) {
            // This causes UnhandledPromiseRejectionWarning warnings:

            const brokenPromises = [createPromises(), createPromises()]

            await brokenPromises[0]
            await brokenPromises[1]

        } else {
            // This works fine:
            await createPromises()
            await createPromises()
        }
    } catch(err) {
        process.stdout.write("x")
    }
}

// Create 10 promises, of which 50% will reject after between 0 and 1000ms
function createPromises() {
    let badPromises = []
    for (let x = 0; x < 10; x++) {
        badPromises.push(new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                process.stdout.write("-")
                reject("rejected!")
            } else {
                process.stdout.write("+")
                resolve()
            }
        }, Math.round(Math.random() * 1000))
        }))
    }

    return Promise.all(badPromises)
}


(async function main() {
    while (true) {
        await doPromises()
    }
})()

Flip the DEMONSTRATE_FAILURE flag to see the same code running with and without the error.

J22
  • 223
  • 1
  • 5

1 Answers1

4

The problem is that as soon as one awaited Promise rejects, an error is thrown, and the code moves from the await brokenPromises[0] line inside the try to the catch. So, the error thrown by brokenPromises[0] has now been properly caught. But, the rejected Promise in brokenPromises[1] has not been handled! (It would have been handled if the try block had passed [0] and gotten to [1], and then the await brokenPromises[0] would have thrown an error which would have been caught, but the interpreter doesn't know that)

Here's a much smaller example that illustrates your original problem (open your browser console to see the rejection, it isn't visible in Stack Snippet console):

const makeProm = () => new Promise((_, reject) => setTimeout(reject, 500));

console.log('will be uncaught:');
// Unhandled rejection:
(async () => {
  try {
    const proms = [makeProm(), makeProm()];
    await proms[0];
    await proms[1];
  } catch(e){}
})();

// Works:
setTimeout(async () => {
  console.log('going to be caught:');
  try {
    await makeProm();
    await makeProm();
  } catch(e){ console.log('caught'); }
}, 1000);

You might fix it by awaiting a Promise.all of both Promises, instead of awaiting each Promise separately - that way, when the Promise.all throws, both rejected Promise chains have been handled.

Changing the process.stdout.writes to console.logs here so the fix can be illustrated in a runnable snippet:

const DEMONSTRATE_FAILURE = true;

async function doPromises() {
  try {
    if (DEMONSTRATE_FAILURE) {
      // This causes UnhandledPromiseRejectionWarning warnings:

      const brokenPromises = [createPromises(), createPromises()]
      await Promise.all(brokenPromises);

    } else {
      // This works fine:
      await createPromises()
      await createPromises()
    }
  } catch (err) {
    console.log("x")
  }
}

// Create 10 promises, of which 50% will reject after between 0 and 1000ms
function createPromises() {
  let badPromises = []
  for (let x = 0; x < 10; x++) {
    badPromises.push(new Promise((resolve, reject) => {
      setTimeout(() => {
        if (Math.random() > 0.5) {
          console.log("-")
          reject("rejected!")
        } else {
          console.log("+")
          resolve()
        }
      }, Math.round(Math.random() * 1000))
    }))
  }

  return Promise.all(badPromises)
}


(async function main() {
    await doPromises()
})()
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320