Avoid uncaught exception when a promise gets rejected due to timeout?

965 Views Asked by At

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()

1

There are 1 best solutions below

3
On BEST ANSWER

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.