3

在使用 Promise 时,很容易忘记在异步函数中使用 try/catch,否则无法捕获所有可能的错误。这会导致无休止的“等待”,即 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
    }
})();```

4

4 回答 4

1

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

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



于 2022-01-26T00:40:35.187 回答
1

在未捕获的同步错误的情况下,Promise 已经拒绝:

  • 在 Promise 构造函数中,用于同步(抛出)错误

    如果在 executor 中抛出错误,则 promise 被拒绝。

  • inonFulfilledonRejected函数,例如 inthencatch

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

  • async函数中

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

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

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


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

于 2022-01-26T00:03:59.837 回答
0

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

function testErrors() {
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(), 1000);
  }).then(() => {
    throw Error("other bad error!");
  }).catch(err => {
    console.log("Catched", err);
  })
}
于 2022-01-26T01:35:29.683 回答
0

可能有一种方法可以做到这一点,但在你的情况下,我认为你真的想reject在你的 Promise 中使用该函数而不是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
    }
})();
于 2022-01-25T22:42:43.547 回答