如何優雅地處理 Async / Await 的異常?

譯者按: 使用.catch()來捕獲全部的異常javascript

本文采用意譯,版權歸原做者全部html

async/await 中的異常處理很讓人混亂。儘管有不少種方式來應對async 函數的異常,可是連經驗豐富的開發者有時候也會搞錯。java

假設你有一個叫作run()的異步函數。在本文中,我會描述 3 種方式來處理run()的異常情形: try/catch, Go 語言風格, 函數調用的時候使用 catch()(即run().catch())。 我會跟你解釋爲何其實幾乎只須要catch()就足夠。node

try/catch

當你第一次使用async/await, 你可能嘗試使用try/catch將每個 async 操做包圍起來。若是你await一個被 reject 的 Promise,JavaScript 會拋出一個能夠被捕獲的錯誤。golang

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)來處理異常。promise

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對一、微脈、青團社等衆多品牌企業。歡迎你們免費試用!

img

版權聲明

轉載時請註明做者 Fundebug以及本文地址:blog.fundebug.com/2019/07/24/…

相關文章
相關標籤/搜索