调试永远不会解决承诺/异步等待
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。
在我正在处理的项目中,我们有时会等待一些永远不会解决的问题,因为糟糕的承诺永远不会解决。
这真的很难找到是哪个 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。