0

By using node js 12.16.1 LTS I don't understand why this piece of code leads to a double rejection (one unhandled and one catched). When I remove the p promise and await p in create_bug(), it works well (Only one rejection catched in a try catch block). I cannot figure out why.

Nodejs experts, could you please help ?

'use strict';

process.on('uncaughtException', (err) => {
  console.error(`uncaughtException: ${JSON.stringify({name: err.name, msg: err.message})}`);
});
process.on('unhandledRejection', (err) => {
  console.error(`unhandledRejection: ${JSON.stringify({name: err.name, msg: err.message})}`);
});

async function create_bug() {
  console.log('In create');
  let res = superCreate();
  console.log(`In create, res = ${res}`);
  let p = new Promise((a, r) => setTimeout(() => a(), 0));
  await p;
  return res;
}


async function superCreate() {
  console.log('superCreate : now throwing');
  throw new Error("Something wrong");
}

async function create_OK() {
  console.log('In create');
  let res = await superCreate();
  console.log(`In create, res = ${res}`);
  let p = new Promise((a, r) => setTimeout(() => a(), 0));
  await p;
  return res;
}

async function main() {
  try {
    let res = await create_bug();
    console.log(`create result : ${res}`);
  } catch (err) {
    console.error(`ERROR caught in main : ${JSON.stringify({name: err.name, msg: err.message})}`);
  }
}

main().then(() => {
  setTimeout(() => console.log(`Finished`), 2000);
});
Guilmort
  • 41
  • 1
  • 5
  • The problem is not that you `await p`, the problem is that you *don't* `await` the `create_bug()` promise - while doing something asynchronous. – Bergi Apr 20 '20 at 21:58
  • create_bug is awaited in main. I try to point out why when I remove both lines from create_bug (let p = ... and await p) I don't have an unhandled promise. – Guilmort Apr 21 '20 at 08:30
  • Oops, I meant to write `superCreate()` (which returns the rejected promise) not `create_bug()`. – Bergi Apr 21 '20 at 09:11

1 Answers1

1

The promise contained in the variable res from your superCreate is not awaited and there is not attached a catch handler to it before it gets rejected. Therefore the unhandled promise rejection is triggered. A handler is attached after the rejection when the await is triggered in main.

Note that a rejection handler is invoked even though it is attached on a promise after it is rejected. Try e.g.:

async function main() {
  let res = create_bug();
  try {
    await res;
    console.log(`create result : ${res}`);
  } catch (err) {
    console.error(`ERROR caught in main : ${JSON.stringify({name: err.name, msg: err.message})}`);
  }
  res.catch(err => console.error(`main1: ${JSON.stringify({name: err.name, msg: err.message})}`));
  res.catch(err => console.error(`main2: ${JSON.stringify({name: err.name, msg: err.message})}`));
}

Notice that you will now also get the "main1" and "main2" errors.

Alternatively, try removing the async from the superCreate function, now you should see that the In create, res = ${res} is not printed, but instead the exception is handled synchronously.

Yet another alternative is to simply return res directly from create_bug without any await and instead await the res in main. Then you will see similar behavior to your original: both an unhandled rejection and the "normal" catch-handling block.

async function create_bug() {
  console.log('In create');
  let res = superCreate();
  console.log(`In create, res = ${res}`);
  return res;
}

async function superCreate() {
  console.log('superCreate : now throwing');
  throw new Error("Something wrong");
}

async function main() {
  try {
    let res = create_bug();
    let p = new Promise((a, r) => setTimeout(() => a(), 0));
    await p;
    await res;
    console.log(`create result : ${res}`);
  } catch (err) {
    console.error(`ERROR caught in main : ${JSON.stringify({name: err.name, msg: err.message})}`);
  }
}
abondoa
  • 1,613
  • 13
  • 23
  • Thanks abondoa. I was not aware about the rejection handler to be invoked even though it is attached on a promise after it is rejected. But how do you explain that when I remove the let p= ... and await p lines from create_bug i don't have the unhandled rejection ? – Guilmort Apr 21 '20 at 08:28
  • When you remove the `let p= ...` and `await p` lines, your `res` is returned (and then awaited in `main`) before it gets to be an unhandled rejection because it all occurs synchronously. Try e.g. moving the `let p= ...` and `await p` lines into your main, so it will be calling `create_bug` (without await), then create and await your `p`, then await `res`. You will here see similar results - i.e. both the undhandled rejection (because `res` fails in `main` now before it is awaited/`catch` is attached) and the "normal" error handling in the `catch`-block. I will update the answer too. – abondoa Apr 21 '20 at 12:54
  • Also, take a look at the linked question/answer, which described this much better than I ever could: https://stackoverflow.com/questions/46889290/waiting-for-more-than-one-concurrent-await-operation – abondoa Apr 21 '20 at 13:04