如何在使用async & await 時優雅的處理異常

原文來自: blog.grossman.io/how-to-writ…javascript

在ES7的中,咱們可使用async & await進行編寫異步函數,使用這種寫法咱們的異步函數看起來就跟同步代碼同樣。java

在以前的版本(ES6),可使用Promise寫法,來簡化咱們異步編程的流程,同時也避免了回調地獄node

回調地獄

回調地獄是語義化產生的一個術語,它的釋義能夠用下面這種狀況進行闡述:git

function AsyncTask() {
   asyncFuncA(function(err, resultA){
      if(err) return cb(err);

      asyncFuncB(function(err, resultB){
         if(err) return cb(err);

          asyncFuncC(function(err, resultC){
               if(err) return cb(err);

               // And so it goes....
          });
      });
   });
}
複製代碼

上例代碼中, 不斷的回調,使得代碼維護和管理控制流程變得十分的困難。 咱們不妨考慮下這種狀況,假如某個if語句須要執行其餘的方法,而回調函數FunctionA的結果爲foo。github

使用Promise優化

ES6和Promise的出現,使得咱們能夠簡化以前"回調地獄"般的代碼以下:數據庫

function asyncTask(cb) {
   asyncFuncA.then(AsyncFuncB)
      .then(AsyncFuncC)
      .then(AsyncFuncD)
      .then(data => cb(null, data)
      .catch(err => cb(err));
}
複製代碼

這樣編寫是否是看起來舒服多了?npm

可是在實際的業務場景中,異步流的處理可能會更加複雜一些。舉例來講,編程

假如在你的一個(node.js)服務器中,你可能想要:數組

  1. 將一個數據1保存到數據庫中 (步驟1)
  2. 根據保存的數據1查找另一個數據2 (步驟2)
  3. 若是查找到了數據2,執行其餘的一些異步任務 (其餘任務)
  4. 等到全部的任務所有執行完成以後,你可能須要使用你在(步驟1)中獲得的結果用來反饋給用戶。
  5. 假如在執行任務的過程當中發生了錯誤,你得要告訴用戶在哪一個步驟發生了錯誤。

在使用了Promise語法後,這樣固然看起來更加的簡潔了,可是,在我看來仍然有一點混亂。promise

ES7 Async/await

您須要使用轉譯器才能使用Async/Await,您可使用babel插件或Typescript來添加所需的工具。

此時, 若使用 async/await, 你會發現代碼寫起來舒服多了. 它容許咱們像下面同樣編寫代碼:

async function asyncTask(cb) {
    const user = await UserModel.findById(1);
    if(!user) return cb('No user found');
    const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
    
    if(user.notificationsEnabled) {
         await NotificationService.sendNotification(user.id, 'Task Created');  
    }
    
    if(savedTask.assignedUser.id !== user.id) {
        await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
    }
    
    cb(null, savedTask);
}
複製代碼

上面的代碼看起來可讀性加強了很多, 可是如何處理錯誤報錯呢?

異常處理

在執行異步任務使用Promise的時候可能會發生一些錯誤相似數據庫鏈接出錯,數據庫模型驗證錯誤等狀況。

當一個異步函數正在等待Promise返回值的時候,當Promise方法報了錯誤的時候,它會拋出異常,這個異常能夠在catch方法裏面捕獲到。

在使用Async/Await時,咱們一般使用try/catch語句進行異常捕獲。

try{
    //do something
}
catch{
   // deal err
}
複製代碼

我沒有編寫強類型語言的背景,所以增長額外的try/catch語句, 對我來講增長了額外的代碼,這在我看來很是的冗餘不乾淨。 我相信這多是我的喜愛的緣由,但這是我對此的見解。

因此以前的代碼看起來像這樣:

async function asyncTask(cb) {
    try {
       const user = await UserModel.findById(1);
       if(!user) return cb('No user found');
    } catch(e) {
        return cb('Unexpected error occurred');
    }

    try {
       const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
    } catch(e) {
        return cb('Error occurred while saving task');
    }

    if(user.notificationsEnabled) {
        try {
            await NotificationService.sendNotification(user.id, 'Task Created');  
        } catch(e) {
            return cb('Error while sending notification');
        }
    }

    if(savedTask.assignedUser.id !== user.id) {
        try {
            await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
        } catch(e) {
            return cb('Error while sending notification');
        }
    }

    cb(null, savedTask);
}
複製代碼

另想辦法

最近我一直在使用go-lang進行編碼,而且很是喜歡他們的解決方案,它的代碼看起來像這樣:

data, err := db.Query("SELECT ...")
if err != nil { return err }
複製代碼

我認爲它比使用try/catch語句塊更加簡潔,而且代碼量更少,這使得它可讀和可維護更好。

可是使用Await的話,若是沒有爲其提供try-catch處理異常的話,當程序發生錯誤的時候,它會默默的退出(你看到不拋出的異常)。假如你沒有提供catch語句來捕捉錯誤的話,你將沒法控制它。

當我和Tomer Barnea(個人好朋友)坐在一塊兒並試圖找到一個更簡潔的解決方案時,咱們獲得了下一個使用方法: 請記住: Await在等待一個Promise返回值

async & await 異常捕捉工具函數

有了這些知識,咱們就能夠製做一個小的通用函數來幫助咱們捕捉這些錯誤。

// to.js
export default function to(promise) {
   return promise.then(data => {
      return [null, data];
   })
   .catch(err => [err]);
}
複製代碼

這個通用函數接收一個Promise,而後將處理成功的返回值以數組的形式做爲附加值返回,而且在catch方法中接收到捕捉到的第一個錯誤。

import to from './to.js';

async function asyncTask(cb) {
     let err, user, savedTask;

     [err, user] = await to(UserModel.findById(1));
     if(!user) return cb('No user found');

     [err, savedTask] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
     if(err) return cb('Error occurred while saving task');

    if(user.notificationsEnabled) {
       const [err] = await to(NotificationService.sendNotification(user.id, 'Task Created'));  
       if(err) return cb('Error while sending notification');
    }

    cb(null, savedTask);
}
複製代碼

上面的例子只是一個使用該解決方案的簡單用例,你能夠在io.js中添加攔截方法(相似調試的斷點),該方法將接收原始錯誤對象,打印日誌或者進行其餘任何你想要進行的操做,而後再返回操做後的對象。

咱們爲這個庫建立了一個簡單的NPM包(Github Repo),您可使用如下方法進行安裝:

npm i await-to-js
複製代碼

這篇文章只是尋找Async/Await功能的一種不一樣方式,徹底基於我的意見。 您可使用Promise,僅使用try-catch和許多其餘解決方案來實現相似的結果。 只要你喜歡而且它適用。

引起思考

async/await 結合promise使用就行了, 即異步流程控制的最後加上 promise的catch.

async function task(){
    return await req();
}

task().catch(e => console.error(e))
複製代碼

結合async/await 結合Promise.all使用時, 如何捕獲異常&處理?

async function hello(flag){
    return new Promise((resolve, reject) => {
        if(flag) setTimeout(() => resolve('hello'), 100);
        else reject('hello-error');
    })
}

async function demo(flag){
    return new Promise((resolve, reject) => {
        if(flag) setTimeout(() => resolve('demo'), 100);
        else reject('demo-error');
    })
}

async function main(){
    let res = await hello(1).catch(e => console.error(e));
    console.log('res => ', res);
    let result = await Promise.all([hello(1), demo(1)]);
    // let result = await Promise.all([hello(1), demo(0)]).catch(e => console.error('error => ', e));
    console.log('result => ', result);
}

main()
複製代碼
相關文章
相關標籤/搜索