1

I have a situation in my node.js program where I have an array of promises. I am prepared to wait a maximum of 200 ms for each promise in the array to get fulfilled, if it’s not fulfilled by then I want it to be rejected.

The code I have written for this works when I run my script in the terminal using node.js without a debugger attached.

However, when I debug the same script using VS code it stops as soon as a promise gets rejected due to timeout. The debugger claims that the rejection is an uncaught exception.

How can I change the code I have such that it does exactly what it does now, but a rejected promise doesn’t cause an exception?

I have tried adding try{} catch{} all over the place but cannot seem to find a solution.

Here is a minimal reproducible example of my issue (the debugger complains about the line reject( "timeout" ) ):

async function delayedPromise(delay) {
  await new Promise((res) => setTimeout(res, delay));
  return "success";
}

function rejectAfterDelay(ms) {
  return new Promise((_, reject) => setTimeout(() => {
    reject("timeout");
  }, ms));
}

async function main() {

  // Create array of promises.
  promArr = [];
  promArr.push(delayedPromise(100));
  promArr.push(delayedPromise(200));
  promArr.push(delayedPromise(300));
  promArr.push(delayedPromise(400));
  promArr.push(delayedPromise(500));

  // Wait for all promises to either get fulfilled or get rejected after 200 ms.
  const msMaxTime = 200;
  const result = await Promise.allSettled(
    promArr.map(promise => Promise.race([promise, rejectAfterDelay(msMaxTime)]))
  );

  console.log(result);
}
main()
Barmar
  • 741,623
  • 53
  • 500
  • 612
Petahanks
  • 182
  • 11
  • "*The debugger claims that the rejection is an uncaught exception.*" - are you sure about that? You didn't enable the "also break on caught exceptions" option in the debugger? Because the rejection *is* handled by the `Promise.race` and/or `Promise.allSettled`. – Bergi May 07 '22 at 16:02
  • 1
    I just debugged it with VS Code and went all fine. Can you give more details? – Christian Tapia May 07 '22 at 16:19
  • So other people don’t have this problem when debugging the example in VS code!? That’s strange. I am debugging with “Uncaught Exceptions” checked and “Caught Exceptions” unchecked in the breakpoint settings in VS code. I’m on Windows 10 using node v.16.13.0 if that’s relevant. For me, the debugger halts in the rejectAfterDelay function with the message “Exception has occurred: timeout” at the line reject(“timeout”). – Petahanks May 07 '22 at 17:02
  • I just tried unchecking “Uncaught Exceptions” and instead checked “Caught Exceptions” in the breakpoint settings. Strangely, the debugger then still stops at the same line of code with the same exception message after changing the settings. When unchecking both uncaught and caught however, the debugger doesn’t halt. But then of course the debugger isn’t much use. – Petahanks May 07 '22 at 17:03

1 Answers1

1

Instead of racing a promise with a short-lived promise(rejectAfterDelay), we can wrap the promise in a short-lived promise:

async function delayedPromise(delay) {
  return new Promise((res) => setTimeout(res, delay, 'success'));
}

// wrap the promise instead of racing it
function rejectAfterDelay(promise, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(reject, ms, 'timeout');
    // forward the reasons to the wrapper
    promise.then(reason => resolve(reason))
           .catch(err => reject(err));
  });
}

async function main() {

  // Create array of promises.
  promArr = [];
  promArr.push(delayedPromise(100));
  promArr.push(delayedPromise(200));
  promArr.push(delayedPromise(300));
  promArr.push(delayedPromise(400));
  promArr.push(delayedPromise(500));

  // Wait for all promises to either get fulfilled or get rejected after 200 ms.
  const msMaxTime = 200;
  const result = await Promise.allSettled(
    promArr.map(promise => {
      //return Promise.race([promise, rejectAfterDelay(msMaxTime)]);
      return rejectAfterDelay(promise, msMaxTime);
    })
  );

  console.log(result.map(r => r.value ? r.value : r.reason));
}
main()

With this the debugger doesn't complain when Uncaught Exceptions option is selected.
Also, depending on your situation, instead of setTimeout(reject, ms, 'timeout') you can use setTimeout(resolve, ms, 'timeout') to make it fail gracefully.

the Hutt
  • 16,980
  • 2
  • 14
  • 44
  • 1
    If you absolutely have to, use `promise.then(resolve, reject)` [instead of `.then(…).catch(…)`](https://stackoverflow.com/q/24662289/1048572). However, this shouldn't make any difference to `Promise.race`? – Bergi May 08 '22 at 14:10
  • @Bergi thanks for the info! I didn't get the question about `Promise.race`, could you elaborate more? – the Hutt May 08 '22 at 15:04
  • The question is why racing to the `resolve`/`reject` calls of a `new Promise` is treated differently by the debugger than `Promise.race`. It really should be equivalent. – Bergi May 08 '22 at 15:08