调试永远不会解决承诺/异步等待

Debugging never resolving promises / async await

在我正在处理的项目中,我们有时会等待一些永远不会解决的问题,因为糟糕的承诺永远不会解决。

这真的很难找到是哪个 await 导致代码停止 运行 并找到 node.js 卡住的位置。

这是问题的代码说明。假设“getRandomlyStuckedPromise”是来自外部库的承诺:

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

如果您已经遇到过此类问题,您是否了解 node.js 中的一些工具或查找卡住等待 node.js 的技术?

您需要以某种方式包装或记录承诺。幸运的是,您可以编写一个非常紧凑的包装器。如果您同时等待多个承诺,您可以将它们排列为一个数组,然后使用 map 将每个承诺包装起来以传递给 Promise.all 或任何您需要它们的地方。

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

根据@JeffBowman 的提议,我做了一个稍微不同的包装 =>

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

这使用错误堆栈跟踪来帮助定位导致问题的 await。