Home > Back-end >  Debugging never resolving promises / async await
Debugging never resolving promises / async await

Time:11-17

On the project I'm working on, we sometimes have some await never resolving due to bad promises never resolving.

This is kind of really hard to find which await is causing the code to stop running and to find where node.js is stuck.

Here is a code illustration of the problem. Let's say "getRandomlyStuckedPromise" is a promise coming from an external library :

getRandomlyStuckedPromise = () => new Promise((resolve) => {
  if (Math.random() > 0.9) return;
  resolve(); // never get called some times
});

await getBadPromise();
await getBadPromise();
await getBadPromise();
await getBadPromise();
await getBadPromise();
await getBadPromise(); // Node.js stuck at one line ... how to know which one ?
await getBadPromise();
await getBadPromise();

If you have already met that kind of issue, do you know some tools in node.js or any technique to find on which await node.js is stuck ?

CodePudding user response:

You'll need to wrap or log the promises one way or another. Luckily you can write a very compact wrapper. If you're awaiting several promises at once, you could arrange them as an array and then use map to wrap each one for passing into Promise.all or wherever you need them.

(async () => {

const getBadPromise = () => new Promise((resolve, reject) => {
  if (Math.random() > 0.8) return;
  if (Math.random() > 0.8) reject(new Error("Promise failed on its own"));
  resolve();
});

/**
 * Logs the given promise with the given label.
 *
 * Will reject the promise after a timeout if it does not return.
 */
function wrap(label, promise) {
  const timeout = 1000;
  // Log promise creation.
  console.log(`Start (${label})`);
  return new Promise((resolve, reject) => {
    // Set up timeout handler to reject.
    const timeoutHandler = setTimeout(() => {
      reject(new Error(`Timeout (${label}, ${timeout})`));
    }, timeout);
    // Promisify value (in case it's a primitive). Once it resolves,
    // log it, clear the timeout, and pass the results out of the function.
    Promise.resolve(promise).finally(() => {
      console.log(`End (${label})`);
      clearTimeout(timeoutHandler);
    }).then(resolve, reject);
  });
}

console.log("Start.");
await wrap("1", getBadPromise());
await wrap("2", getBadPromise());
await wrap("3", getBadPromise());
await wrap("4", getBadPromise());
await wrap("5", getBadPromise());
await wrap("6", getBadPromise());
await wrap("7", getBadPromise());
await wrap("8", getBadPromise());
await wrap("9", getBadPromise());
console.log("Done.");

})().catch(console.error);
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

Based on @JeffBowman proposition i made a slightly different wrapper =>

const withRejectTimeout = (promise, timeout = 120000 /* 2mn */) => {
  const timeoutError = new Error('Promise timeout');

  return Promise.race([
    promise,
    new Promise((_, reject) => setTimeout(() => reject(timeoutError), timeout)),
  ]);
};

await withRejectTimeout(getBadPromise());
await withRejectTimeout(getBadPromise());
await withRejectTimeout(getBadPromise());
await withRejectTimeout(getBadPromise());
await withRejectTimeout(getBadPromise());
await withRejectTimeout(getBadPromise());
await withRejectTimeout(getBadPromise());

This use the error stack trace to help locate the await causing problem.

  • Related