快速错误处理和异步等待

Posted

技术标签:

【中文标题】快速错误处理和异步等待【英文标题】:Express error handling and async await 【发布时间】:2017-05-25 00:19:26 【问题描述】:

在我的 Node.js 应用程序中,我添加了以下代码来捕获每个未捕获的异常:

process.on('uncaughtException', function (err: Error) 
    try 
        logger.err(err);
     catch (err) 

    
);

问题在于 Express 有自己的默认错误处理程序,它会捕获每个未捕获的异常。现在,Express 在 Node (process.on) 之前捕获了异常,所以我的记录器没有被访问。但是,可以添加另一个可以在 Express 之前捕获每个异常的错误处理程序:

app.use(logErrors);

function logErrors (err: Error, req: Request, res: Response, next: NextFunction) 
    logger.err(err);
    next(err);

这仍然不能涵盖所有情况。每当我有一个用await 调用的async function 时,也不例外,但会返回一个被拒绝的Promise。例如:

app.get('/foo', async function (req: Request, res: Response, next: NextFunction) 
    await bar();
);

function bar() 
    throw new Exception();

不会到达我的 logErrors 函数,因为它不会抛出,但会返回一个被拒绝的 Promise。

为了解决这个问题,我用另一个函数包装了我的 Express HTTP 处理程序:

app.get('/foo', wrap(async function (req: Request, res: Response, next: NextFunction) 
    await bar();
));

function wrap(func: (req: Request, res: Response, next: NextFunction) => void) 
    return async function (req: Request, res: Response, next: NextFunction) 
        try 
            await func(req, res, next);
        
        catch (err) 
            next(err);
        
    

next(err) 将错误传递给我的处理程序。现在,我设法捕获了 logErrors 函数的异常。

我快完成了。我仍然有一种情况无法发现错误。当我在没有 await 关键字的情况下调用 async function 时会发生这种情况(有时在代码中的两个不同位置使用相同的函数很有用,一次是异步调用,一次是同步调用)。所以这段代码不会捕捉到错误:

app.get('/foo', wrap(async function (req: Request, res: Response, next: NextFunction) 
    bar();
));

这里发生的是 Express HTTP 处理程序将已解析的 Promise 返回给 wrap 函数。 wrap 函数又不会到达 catch 块,因此它不会调用 next(err) 会到达我的记录器。

bar 函数依次返回一个被拒绝的 Promise,但没有人在等待它的返回值。

我怎样才能更改我的代码,以免最终导致任何未处理的 Promise 拒绝? (仅通用解决方案)

【问题讨论】:

我认为同步调用异步函数是个坏主意,实际上不可能。您可以忽略该函数是异步的事实并调用它,但它仍将异步运行,您只会遇到您描述的问题。所以我的建议是不要以同步方式使用异步函数——它们是完全不同的野兽。 @Amid 他们并没有真正同步。我的意思是:当我在没有“等待”的情况下调用它时是异步的。当我用'await'调用它时是同步的(不是真的,但有点:代码是逐行执行的,但是每次我在后台调用promise时线程都可以处理其他请求)。 这就是我的意思。通过在没有等待的情况下调用异步函数,您会遇到此类问题。它们实际上不应该以这种方式调用,而您的示例中的异常实际上不是异常,但承诺拒绝是一个很好的说明。因此,即使它们是某个次要事件处理程序的最后一行,我也不建议这样做——使用 await 调用它们仍然是一个好习惯。 @Amid 你是对的,我同意你的观点,我总是用'await'调用异步函数,即使它是任务的最后一行。我这样做是因为我想在不让他等待的情况下向客户返回响应。我还能怎么做?我听说调用 res.end() 不是一个好主意,因为它会跳过应该执行的中间件。 我想你必须选择这里。要么您将执行返回给客户端,它不会获得有关错误/异常的任何信息。或者您等待异步操作完成并获得所有可用信息。 【参考方案1】:

还有另一个process.on 事件,您可以为其设置侦听器 - unhandledRejection。

您可以使用它来处理所有代码中的这些拒绝。

注意:记得在记录完所有需要的内容后终止进程。更多关于这个here。

【讨论】:

【参考方案2】:

我找到了解决办法:

app.get('/foo', async function (req: Request, res: Response, next: NextFunction) 
    dontAwait(() => bar());
);

async function dontAwait(func: () => void) 
   try 
       await func();
   
   catch (err) 
     logErrors(err);
   

【讨论】:

【参考方案3】:

我仍然有一种情况无法捕捉到错误。

你可以,但你目前根本不对结果做任何事情。如果您希望触发通常的错误处理程序,您需要await它。

当我调用没有await 关键字的async function 时会发生这种情况

我看不出有什么好的理由。但是如果你真的想fire and forget the function,你可以做到。你只需要明确地处理错误:

bar().catch(e => logger.err(e));

【讨论】:

感谢您的回复。你的建议比我的好,但我还是更喜欢基里尔的建议。顺便说一句,我想在没有 await 关键字的情况下调用异步函数是有充分理由的。您可以在问题下方的 cmets 中看到我与 Amid 的通信。 OK,“我想在不让他等待的情况下向客户返回响应。”是一个合理的用例,但很少见。但是,我不认为这是“一劳永逸”的情况,因为您可能想对调用的结果(成功或失败)做一些事情:存储它并通知用户您已完成。 unhandledRejection 跟踪不是一个好的解决方案,因为它确实松散了上下文。是的,记录它们是个好主意,但你不应该期待它们。 Bergi 你是对的,unhandledRejection 通常只对记录有用。当用户需要启动一个他不应该在 HTTP 响应中得到结果的后台操作时,调用不带“等待”的函数很有用。他可以稍后通过电子邮件或彗星/投票获得结果。在这个过程中,我会处理我能想到的每一种失败。这种通用错误处理只应该记录我没想到的故障。在这种情况下,我将不得不按照基里尔的说法重新启动我的应用程序,最后我必须修复我的代码以处理这些故障。 是的,对于该用例,请使用 unhandledRejections。让我失望的是你想调用默认的 Express 错误处理程序,所以我认为你仍然需要一些特定于请求的错误处理。

以上是关于快速错误处理和异步等待的主要内容,如果未能解决你的问题,请参考以下文章

Discord bot异步/等待错误处理

使用 axios 处理来自异步等待语法的错误

基于任务的异步编程模式(TAP)的错误处理

Day650.MQ异步处理问题 -Java业务开发常见错误

将 Node.js 流错误事件传播到异步等待样式代码

在 ExpressJS 响应中返回异步等待 try/catch 抛出错误 [重复]