【问题标题】:Any way to cause a promise to be rejected if it has an uncaught error?如果有未捕获的错误,有什么方法可以导致 promise 被拒绝?
【发布时间】:2022-01-26 09:35:29
【问题描述】:

很容易忘记在异步函数中使用 try/catch,否则在使用 Promise 时无法捕获所有可能的错误。这可能会导致无休止的“等待”,因为 Promise 永远不会被解决也不会被拒绝。

如果存在未捕获的错误,是否有任何方法(例如通过代理或更改 Promise 构造函数)导致异步函数或其他 Promise 被拒绝?下面显示一个一般情况。我正在寻找某种方法来通过“等待”(如在抛出错误时应该拒绝“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 的拒绝函数(res 之后的第二个参数)而不是 throw 吗?
  • 不改变承诺就无法改变承诺...

标签: javascript promise uncaughtexceptionhandler


【解决方案1】:

如果发生未捕获的同步错误,Promise 已经拒绝:

  • in a Promise constructor,用于同步(抛出)错误

    如果在执行器中抛出错误,则 promise 被拒绝。

  • in onFulfilled and onRejected functions,如thencatch

    如果处理函数:[...] 抛出错误,then 返回的承诺将被拒绝,并以抛出的错误作为其值。

  • in async functions

    返回值:Promise,将使用异步函数返回的值进行解析,或因异步函数抛出或未捕获的异常而被拒绝。

您的问题不是 Promise 不处理未捕获的错误,而是根本上因为您的错误是异步:就 Promise 而言,它的 executor 函数是一个成功的小函数,致电setTimeout。当您的 setTimeout 处理程序运行并失败时,它会使用与 Promise 对象或其函数无关的自己的堆栈来执行此操作; setTimeout 处理程序中不存在与badPromisep 相关的任何内容,除了处理程序通过闭包包含的res 引用。与问题“Handle error from setTimeout”一样,在setTimeout 处理程序中捕获错误的技术都涉及编辑或包装处理程序,并且根据HTML spec for timers 步骤9.2,没有机会捕获或插入调用的错误案例传递给setTimeout的函数。

除了编辑badPromise,您几乎无能为力。


(我能想到的唯一替代方法是依次修改/覆盖 Promise 构造函数和 setTimeout 方法,包装 Promise 构造函数的方法以保存 resolve/reject 参数,然后包装全局setTimeout 方法以便用调用新保存的reject 参数的try/catch 包装setTimeout 处理程序。由于更改这两个全局服务的脆弱性,我强烈建议不要使用任何像这样的解决方案。

【讨论】:

  • 谢谢,非常感谢您的详细反馈!
【解决方案2】:

根本问题是计时器回调作为顶级代码运行,检测其中错误的唯一方法是侦听全局错误事件。这是一个使用全局处理程序来检测此类错误的示例,但它存在一些问题,我将在下面的代码中讨论:

"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,您可能会得到不同的参数。
  • 在示例中,任何冒泡到window 的错误事件都可以赢得承诺竞赛。 它不需要在badPromise() 调用中生成。
  • 如果对badPromise 的多个调用同时处于活动状态,则捕获全局错误不会告诉您哪个badPromise 调用出错。

因此,badPromise 确实是糟糕,需要戴上童手套处理。如果您严重无法修复它,您可能需要确保您只有一个未完成的调用,并且您没有做任何其他可能同时产生全局错误的操作。我无法评论您的情况是否可行。

替代方案

更通用的替代方法可能是在调用 badPromise 之前启动一个计时器,并使用它来超时返回的 Promise 的挂起状态;

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



【讨论】:

  • 更通用:如果可以避免,请不要在计时器中运行代码。 (在导入使用计时器运行代码的旧 JavaScript 代码时,您不能这样做。否则您可以。)
  • 感谢您的详细反馈!这是一个艰难的问题,在这种情况下,修复承诺是不可能的,因为我提出问题的原因是为了帮助管理编码错误(即,如果我在事故)。
【解决方案3】:

可能有一种方法可以做到这一点,但在你的情况下,我认为你真的想在 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
    }
})();

【讨论】:

  • 虽然这是个好建议,但 OP 确实说,“不修复 badPromise”。
  • 这个错误是为了说明问题而故意犯的。
【解决方案4】:

也许不是你想要的答案,但你可以为setTimeout 使用这样的模式:

function testErrors() {
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(), 1000);
  }).then(() => {
    throw Error("other bad error!");
  }).catch(err => {
    console.log("Catched", err);
  })
}

【讨论】:

  • 你实际上不理解 OP 的问题,OP 不希望人们修复 badPromise 而是尝试捕获其中的错误。
  • @ikhvjs 啊,谢谢。 (但请再次阅读我的回答。)
猜你喜欢
  • 2017-02-05
  • 2016-06-21
  • 2012-05-05
  • 1970-01-01
  • 2019-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-18
相关资源
最近更新 更多