如何优雅地处理Async/Await的异常?

译者按: 使用.catch()来捕获所有的异常

本文采用意译,版权归原作者所有

async/await 中的异常处理很让人混乱。尽管有很多种方式来应对async 函数的异常,但是连经验丰富的开发者有时候也会搞错。

假设你有一个叫做run()的异步函数。在本文中,我会描述 3 种方式来处理run()的异常情形: try/catch, Go 语言风格, 函数调用的时候使用 catch()(即run().catch())。 我会跟你解释为什么其实几乎只需要catch()就足够。

try/catch

当你第一次使用async/await, 你可能尝试使用try/catch将每一个 async 操作包围起来。如果你await一个被 reject 的 Promise,JavaScript 会抛出一个可以被捕获的错误。

run();

async function run() {
    try {
        await Promise.reject(new Error("Oops!"));
    } catch (error) {
        error.message; // "Oops!"
    }
}

try/catch 能够捕获非异步的异常。

run();

async function run() {
    const v = null;
    try {
        await Promise.resolve("foo");
        v.thisWillThrow;
    } catch (error) {
        // "TypeError: Cannot read property 'thisWillThrow' of null"
        error.message;
    }
}

所以,只需要将所有的代码逻辑都用 try/catch包围起来就可以搞定?也不完全正确。下面的代码会抛出unhandled promise rejection. await将一个被拒绝的 promise 转换为可捕获的错误,但是 return 不行。

run();

async function run() {
    try {
        // 注意这里是return,不是await
        return Promise.reject(new Error("Oops!"));
    } catch (error) {
        // 代码不会执行到这里
    }
}

也不可能使用 return await来绕开。

还有一个缺点就是使用了try/catch 之后,就很难用.的语法来进行 Promise 链式组合了。

使用 Go 的语法

另一个常见的方式就是使用then()将一个本来需要用catch()来捕获并处理的 Promise 转换为普通的 Promise。然后像 Go 语言中一样,使用if(err)来处理异常。

run();

async function throwAnError() {
    throw new Error("Oops!");
}

async function noError() {
    return 42;
}

async function run() {
    // The `.then(() => null, err => err)` 来匹配正常/异常的情况。如果正常情况,返回`null`;如果异常,返回`err`
    let err = await throwAnError().then(() => null, err => err);
    if (err != null) {
        err.message; // 'Oops'
    }

    err = await noError().then(() => null, err => err);
    err; // null
}

如果你真的想要同时返回 error 和正确的值,你可以完全假装在用 Go 语言。

run();

async function throwAnError() {
    throw new Error("Oops!");
}

async function noError() {
    return 42;
}

async function run() {
    // The `.then(v => [null, v], err => [err, null])` pattern
    // 你可以使用数组解构来匹配err和返回值
    let [err, res] = await throwAnError().then(
        v => [null, v],
        err => [err, null]
    );
    if (err != null) {
        err.message; // 'Oops'
    }

    err = await noError().then(v => [null, v], err => [err, null]);
    err; // null
    res; // 42
}

使用 Go 语言风格的错误处理并不能摆脱return无法捕获的情况。而且还让整个代码更加的复杂,如果忘记if(err != null),就会出问题。

总的来说,有两大缺点:

  1. 代码极度重复,每一个地方都少不了if (err != null) ,真的很累,而且容易漏掉;
  2. run()函数中的非异步的错误也无法处理;

总的来说,它并没有比try/catch好多少。

在函数调用的时候使用catch()

try/catch 和 Go 语言风格的异常处理都有各自的使用场景,但是处理所有异常最好的方法是在run()函数的后面使用catch(),像这样:run().catch()。换句话说,用一个catch()来处理run函数中的所有错误,而不是针对run里面的每一种情况都去写代码做相应的处理。

run()
    .catch(function handleError(err) {
        err.message; // Oops!
    })
    // 在handleError中处理所有的异常
    // 如果handleError出错,则退出。
    .catch(err => {
        process.nextTick(() => {
            throw err;
        });
    });

async function run() {
    await Promise.reject(new Error("Oops!"));
}

记住,async 函数总是返回 promise。只要函数中有异常,Promise 会 reject。而且,如果一个 async 函数返回的是一个 reject 的 Promise,那么这个 Promise 依然会继续被 reject。

run()
    .catch(function handleError(err) {
        err.message; // Oops!
    })
    .catch(err => {
        process.nextTick(() => {
            throw err;
        });
    });

async function run() {
    // 注意:这里使用了return,而不是await
    return Promise.reject(new Error("Oops!"));
}

为什么使用run().catch()而不是将整个run()函数用try/catch包起来呢?我们首先来考虑一个情况:如果try/catchcatch部分有异常,我们应该如何处理呢?只有一个方法:在catch里面接着使用try/catch。所以,run().catch()的模式使得异常处理变得非常简洁。

总结

我们最好是全局的有一个 errorHandler 来处理那些没有考虑到的异常,比如使用run().catch(handleError),而不是在run()函数里面所有可能出错的地方加上try/catch

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!

版权声明

转载时请注明作者 Fundebug以及本文地址:https://blog.fundebug.com/2019/07/24/async-await-error-handling-in-js/

原文地址:https://www.cnblogs.com/fundebug/p/async-await-error-handling-in-js.html

时间: 2024-10-10 11:38:01

如何优雅地处理Async/Await的异常?的相关文章

微信小程序捕获async/await函数异常实践

背景 我们的小程序项目的构建是与web项目保持一致的,完全使用webpack的生态来构建,没有使用小程序自带的构建功能,那么就需要我们配置代码转换的babel插件如Promise.Proxy等:另外,项目中涉及到异步的功能我们统一使用async/await来处理.我们知道,小程序的onError 生命周期只能捕获同步错误,而完全不采用小程序自带构建工具的情况下,开发模式下遇到的问题: 小程序异步代码中的异常onError无法捕获,开发者工具控制台也没有抛出异常信息 这样在开发过程中页面展示异常,

优雅地 `async/await`

async/await 虽然取代了回调,使用类似同步的代码组织方式让代码更加简洁美观,但错误处理时需要加 try/catch. 比如下面这样,一个简单的 Node.js 中使用 async/await 的场景: const fetch = require("node-fetch"); async function getData() { const url = "https://api.github.com/users/wayou"; try { const res

进阶篇:以IL为剑,直指async/await

接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理. 先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅的写异步操作代码,它本身并不会去创建一个新线程,线程的工作还是由Task来做,async/await只是让开发人员以直观的方式写异步操作代码,而不像以前那样到处都是callback或事件. async/await IL翻译 先写个简单的例子: 1 using System; 2 using Syste

callback vs async.js vs promise vs async / await

需求: A.依次读取 A|B|C 三个文件,如果有失败,则立即终止. B.同时读取 A|B|C 三个文件,如果有失败,则立即终止. 一.callback 需求A: let read = function (code) { if (code) { return true; } else { return false; } } let readFileA = function (callback) { if (read(1)) { return callback(null, "111");

Async/Await替代Promise的6个理由

译者按: Node.js的异步编程方式有效提高了应用性能:然而回调地狱却让人望而生畏,Promise让我们告别回调函数,写出更优雅的异步代码:在实践过程中,却发现Promise并不完美:技术进步是无止境的,这时,我们有了Async/Await. 原文: 6 Reasons Why JavaScript’s Async/Await Blows Promises Away 译者: Fundebug 为了保证可读性,本文采用意译而非直译. Node.js 7.6已经支持async/await了,如果你

Atitit. Async await 优缺点 异步编程的原理and实现 java c# php

Atitit. Async await 优缺点 异步编程的原理and实现 java c# php 1. async & await的来源1 2. 异步编程history1 2.1. 线程池 2 2.2. 返回值2 2.3. Semaphore 信号量2 2.4. 线程的异常,主线程可以捕获到么2 3. await并不是针对于async的方法,而是针对async方法所返回给我们的Task,2 4. Java里面的task 跟个 await3 5. ---code4 6. async & aw

【转载】async & await 的前世今生(Updated)

async 和 await 出现在C# 5.0之后,给并行编程带来了不少的方便,特别是当在MVC中的Action也变成async之后,有点开始什么都是async的味道了.但是这也给我们编程埋下了一些隐患,有时候可能会产生一些我们自己都不知道怎么产生的Bug,特别是如果连线程基础没有理解的情况下,更不知道如何去处理了.那今天我们就来好好看看这两兄弟和他们的叔叔(Task)爷爷(Thread)们到底有什么区别和特点,本文将会对Thread 到 Task 再到 .NET 4.5的 async和 awa

ES6 async/await语法

Async/await的主要益处是可以避免回调地狱(callback hell)问题 Chromium JavaScript引擎 从v5.5开始支持async/await功能,Chromium JavaScript引擎版本号的查看方法是:在浏览器地址栏输入 chrome://version,查询出来的信息如下图所示: 这意味着async/await不再是实验性功能,使用它时无需指定--harmony参数,此参数用来开启那些几乎已完成但被V8团队认为还不稳定的功能.不同的node版本使用不同的V8

node.js异步控制流程 回调,事件,promise和async/await

写这个问题是因为最近看到一些初学者用回调用的不亦乐乎,最后代码左调来又调去很不直观. 首先上结论:推荐使用async/await或者co/yield,其次是promise,再次是事件,回调不要使用. 接下来是解析,为什么我会有这样的结论 首先是回调,理解上最简单,就是我把任务分配出去,当你执行完了我就能从你那里拿到结果执行相应的回调, 这里演示一个对setTimeout的封装,规定时间后打印相应结果并执行回调函数 并且这个函数传给回调函数的参数符合node标准,第一个为error信息,如果出错e