如果承诺有未捕获的错误,有什么方法可以导致承诺被拒绝?

Any way to cause a promise to be rejected if it has an uncaught error?

提问人:Robert T 提问时间:1/26/2022 更新时间:9/23/2022 访问量:1702

问:

在使用 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
    }
})();```

JavaScript Promise uncaughtExceptionHandler

评论

0赞 Christian Fritz 1/26/2022
难道你不应该使用 Promise 的 reject 函数(res之后的第二个参数)而不是 throw 吗?
1赞 Heretic Monkey 1/26/2022
如果不改变承诺,就没有办法改变承诺......

答:

0赞 Christian Fritz 1/26/2022 #1

可能有一种方法可以做到这一点,但在您的情况下,我认为您真的想使用 Promise 中的函数而不是 .这才是真正的拒绝。rejectthrow

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

评论

0赞 Heretic Monkey 1/26/2022
虽然这是一个很好的建议,但 OP 确实说,“不修复 badPromise”。
0赞 SamiElk 1/26/2022
这个错误是为了证明这个问题而故意犯的。
2赞 Jeff Bowman 1/26/2022 #2

在未捕获的同步错误的情况下,承诺已被拒绝:

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

    如果在执行程序中抛出错误,则承诺将被拒绝。

  • onFulfilledonRejected 函数中,例如 in 和thencatch

    如果处理程序函数:[...] 引发错误,则返回的 promise 将被拒绝,并将引发的错误作为其值。then

  • 在异函数中

    返回值:A 将使用异步函数返回的值进行解析,或拒绝异步函数,并引发异常或在异步函数中未捕获。Promise

你在这里的问题不在于 Promise 不处理未捕获的错误,而在于从根本上说,你的错误是异步的:就 Promise 而言,它的执行器函数是一个成功的小函数,它调用 .当处理程序运行并失败时,它使用自己的堆栈来运行,该堆栈与 Promise 对象或其函数无关;除了处理程序通过闭包包含的引用外,没有与处理程序相关或存在于处理程序中。与问题“处理来自 setTimeout 的错误”一样,在处理程序中捕获错误的技术都涉及编辑或包装处理程序,并且根据计时器步骤 9.2 的 HTML 规范,没有机会捕获或插入调用传入的函数的错误情况。setTimeoutsetTimeoutbadPromisepsetTimeoutressetTimeoutsetTimeout

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


选择:

  • 按顺序修改/覆盖 Promise 构造函数和方法,包装 Promise 构造函数的方法以保存 / 参数,然后包装全局方法,以便将处理程序包装为调用新保存参数的 /。由于改变这两种全球服务的脆弱性,我强烈建议不要使用任何这样的解决方案。setTimeoutresolverejectsetTimeoutsetTimeouttrycatchreject

  • 创建一个包装器高阶函数(即返回函数的函数),该函数接受拒绝回调并包装 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');
    
      

评论

0赞 Robert T 2/13/2022
谢谢,我真的很感谢详细的反馈!
1赞 traktor 1/26/2022 #3

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

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

})();

问题

  • 编写代码时不关心传递给使用添加的全局错误处理程序的内容 - 如果您使用 .addEventListenerwindow.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);



评论

0赞 Leo 1/30/2022
更通用的是:如果可以避免,请不要在计时器中运行代码。(导入使用计时器运行代码的旧 JavaScript 代码时,不能这样做。但除此之外,你可以。
0赞 Robert T 2/13/2022
感谢您的详细反馈!这是一个艰难的问题,在这种情况下,修复承诺是不可能的,因为我提出问题的原因是帮助管理编码错误(即,如果我在意外中创建类似“badPromise”的东西,我正在努力确保我得到更明确的反馈)。
0赞 Leo 1/26/2022 #4

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

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

评论

0赞 ikhvjs 1/26/2022
你实际上不明白OP的问题,OP不希望人们修复badPromise,而是试图抓住其中的错误。
0赞 Leo 1/30/2022
@ikhvjs 啊,谢谢。(但请再读一遍我的回答。