提问人:Robert T 提问时间:1/26/2022 更新时间:9/23/2022 访问量:1702
如果承诺有未捕获的错误,有什么方法可以导致承诺被拒绝?
Any way to cause a promise to be rejected if it has an uncaught error?
问:
在使用 promise 时,很容易忘记在异步函数中使用 try/catch,或者以其他方式无法捕获所有可能的错误。这可能会导致无休止的“等待”,因为承诺永远不会被解决或拒绝。
如果存在未捕获的错误,是否有任何方法(例如通过代理或更改 promise 构造函数)会导致异步函数或其他承诺被拒绝?下面显示了一个广义案例。我正在寻找某种方法来克服“await”(因为在抛出错误时应该拒绝“p”),而无需修复“badPromise”。
async function badPromise() {
const p = new Promise((res) => {
delayTimer = setTimeout(() => {
console.log('running timeout code...');
if (1 > 0) throw new Error('This is NOT caught!'); // prevents the promise from ever resolving, but may log an error message to the console
res();
}, 1000);
});
return p;
}
(async () => {
try {
console.log('start async');
await badPromise();
console.log('Made it to the end'); // never get here
} catch (e) {
console.error('Caught the problem...', e); // never get here
}
})();```
答:
可能有一种方法可以做到这一点,但在您的情况下,我认为您真的想使用 Promise 中的函数而不是 .这才是真正的拒绝。reject
throw
async function badPromise() {
const p = new Promise((res, reject) => {
delayTimer = setTimeout(() => {
console.log('running timeout code...');
if (1 > 0) {
reject('This is NOT caught!');
return;
}
res();
}, 1000);
});
return p;
}
(async () => {
try {
console.log('start async');
await badPromise();
console.log('Made it to the end'); // never gets here
} catch (e) {
console.error('Caught the problem...', e); // should work now
}
})();
评论
在未捕获的同步错误的情况下,承诺已被拒绝:
- 在 Promise 构造函数中,用于同步(抛出)错误
如果在执行程序中抛出错误,则承诺将被拒绝。
- 在
onFulfilled
和onRejected
函数中,例如 in 和then
catch
如果处理程序函数:[...] 引发错误,则返回的 promise 将被拒绝,并将引发的错误作为其值。
then
- 在异
步
函数中返回值:A 将使用异步函数返回的值进行解析,或拒绝异步函数,并引发异常或在异步函数中未捕获。
Promise
你在这里的问题不在于 Promise 不处理未捕获的错误,而在于从根本上说,你的错误是异步的:就 Promise 而言,它的执行器函数是一个成功的小函数,它调用 .当处理程序运行并失败时,它使用自己的堆栈来运行,该堆栈与 Promise 对象或其函数无关;除了处理程序通过闭包包含的引用外,没有与处理程序相关或存在于处理程序中。与问题“处理来自 setTimeout 的错误”一样,在处理程序中捕获错误的技术都涉及编辑或包装处理程序,并且根据计时器步骤 9.2 的 HTML 规范,没有机会捕获或插入调用传入的函数的错误情况。setTimeout
setTimeout
badPromise
p
setTimeout
res
setTimeout
setTimeout
除了编辑,你几乎无能为力。badPromise
选择:
按顺序修改/覆盖 Promise 构造函数和方法,包装 Promise 构造函数的方法以保存 / 参数,然后包装全局方法,以便将处理程序包装为调用新保存参数的 /。由于改变这两种全球服务的脆弱性,我强烈建议不要使用任何这样的解决方案。
setTimeout
resolve
reject
setTimeout
setTimeout
try
catch
reject
创建一个包装器高阶函数(即返回函数的函数),该函数接受拒绝回调并包装 setTimeout 调用。从技术上讲,这是对 的编辑,但它确实封装了正在更改的内容。它看起来像这样:
badPromise
function rejectOnError(rej, func) { return (...args) => { try { return func(...args); } catch (e) { rej(e); } }; } async function badPromise() { const p = new Promise((res, rej) => { // save reject delayTimer = setTimeout(rejectOnError(rej, () => { // to use here console.log('running timeout code...'); if (1 > 0) throw new Error('Now this is caught'); res(); }), 1000); }); return p; } badPromise().catch(x => console.error(`outer: ${x}`)); console.log('bad promise initiated');
评论
根本问题是计时器回调作为顶级代码运行,检测其中错误的唯一方法是侦听全局错误事件。下面是使用全局处理程序检测此类错误的示例,但它存在一些问题,我将在代码下方讨论这些问题:
"use strict";
let delayTimer; // declare variable
async function badPromise() {
const p = new Promise((res) => {
let delayTimer = setTimeout(() => { // declare variable!!!
console.log('running timeout code...');
if (1 > 0) throw new Error('This is NOT caught!'); // prevents the promise from ever resolving, but may log an error message to the console
res();
}, 1000);
});
return p;
}
(async () => {
let onerror;
let errorArgs = null;
let pError = new Promise( (res, rej)=> {
onerror = (...args) => rej( args); // error handler rejects pError
window.addEventListener("error", onerror);
})
.catch( args => errorArgs = args); // Catch handler resolves with error args
// race between badPromise and global error
await Promise.race( [badPromise(), pError] );
window.removeEventListener("error", onerror); // remove global error handler
console.log("Made it here");
if( errorArgs) {
console.log(" but a global error occurred, arguments array: ", errorArgs);
}
})();
问题
- 编写代码时不关心传递给使用添加的全局错误处理程序的内容 - 如果您使用 .
addEventListener
window.onerror = errorHandler
- 在示例中冒泡的任何错误事件都可以赢得承诺竞赛。它不需要在
badPromise()
调用中生成。window
- 如果对
badPromise
的多个调用同时处于活动状态,则捕获全局错误不会告诉您哪个badPromise
调用出错。
因此真的很糟糕,需要戴上儿童手套。如果您真的无法修复它,您可能需要确保只有一个未完成的调用,并且您没有做任何其他可能同时产生全局错误的操作。在你的情况下,这是否可行,我无法评论。badPromise
另类
更通用的替代方法可能是在调用之前启动计时器,并使用它来超时返回的 promise 的挂起状态;badPromise
let timer;
let timeAllowed = 5000;
let timedOut = false;
let timeout = new Promise( res => timer = setTimeout(res, timeAllowed))
.then( timedOut = true);
await Promise.race( [badPromise(), timeout])
clearTimer( timer);
console.log( "timed out: %s", timedOut);
评论
也许不是你想要的答案,但你可以使用这样的模式:setTimeout
function testErrors() {
new Promise((resolve, reject) => {
setTimeout(() => resolve(), 1000);
}).then(() => {
throw Error("other bad error!");
}).catch(err => {
console.log("Catched", err);
})
}
评论