摘要:像這種中斷式的錯誤處理,其實正適合采用。然而注意,上面的代碼中并沒有直接使用,而是使用了自定義的錯誤消息。所以需要對出來的進一步處理成指定的錯誤消息。
前不久看到 Dima Grossman 寫的 How to write async await without try-catch blocks in Javascript。看到標題的時候,我感到非常好奇。我知道雖然在異步程序中可以不使用 try-catch 配合 async/await 來處理錯誤,但是處理方式并不能與 async/await 配合得很好,所以很想知道到底有什么辦法會比 try-catch 更好用。
Dima 去除 try-catch 的方法當然套路依舊,Dima 講到了回調地獄,Promise 鏈并最終引出了 async/await。而在處理錯誤的時候,他并不喜歡 try-catch 的方式,所以寫了一個 to(promise) 來對 Promise 進行封裝,輔以解構語法,實現了同步寫法但類似 Node 錯誤標準的代碼。摘抄代碼如下
// to.js export default function to(promise) { return promise .then(data => { return [null, data]; }) .catch(err => [err]); }
應用示例:
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); }
Dima 的辦法讓人產生的了熟悉的感覺,Node 的回調中不是經常都這樣寫嗎?
(err, data) => { if (err) { // deal with error } else { // deal with data } }
所以這個方法真的很有意思。不過回過頭來想一想,這段代碼中每當遇到錯誤,都是將錯誤消息通過 cb() 調用推出去,同時中斷后續過程。像這種中斷式的錯誤處理,其實正適合采用 try-catch。
使用 try-catch 改寫上面的代碼要用 try-catch 改寫上面的代碼,首先要去掉 to() 封裝。這樣,一旦發生錯誤,需要使用 Promise.prototype.catch() 進行捕捉,或者使用 try-catch 對 await promise 語句進行捕捉。捕捉到的,當然是每個業務代碼里 reject 出來的 err。
然而注意,上面的代碼中并沒有直接使用 err,而是使用了自定義的錯誤消息。所以需要對 reject 出來的 err 進一步處理成指定的錯誤消息。當然這難不到誰,比如
someAsync().catch(err => Promise.reject("specified message"));
然后再最外層加上 try-catch 就好。所以改寫之后的代碼是:
async function asyncTask(cb) { try { const user = await UserModel.findById(1) .catch(err => Promise.reject("No user found")); const savedTask = await TaskModel({ userId: user.id, name: "Demo Task" }) .catch(err => Promise.reject("Error occurred while saving task")); if (user.notificationsEnabled) { await NotificationService.sendNotification(user.id, "Task Created") .catch(err => Promise.reject("Error while sending notification")); } cb(null, savedTask); } catch (err) { cb(err); } }
上面這段代碼,從代碼量上來說,并沒有比 Dima 的代碼減少了多少工作量,只是去掉了大量 if (err) {} 結構。不習慣使用 try-catch 的程序員找找不到中斷點,但習慣了 try-catch 的程序員都知道,業務過程中一旦發生錯誤(異步代碼里指 reject),代碼就會跳到 catch 塊去處理 reject 出來的值。
但是,一般業務代碼 reject 出來的信息通常都是有用的。假如上面的每個業務 reject 出來的 err 本身就是錯誤消息,那么,用 Dima 的模式,仍然需要寫
if (err) return cb(err);
而用 try-catch 的模式,就簡單多了
async function asyncTask(cb) { try { const user = await UserModel.findById(1); const savedTask = await TaskModel({ userId: user.id, name: "Demo Task" }); if (user.notificationsEnabled) { await NotificationService.sendNotification(user.id, "Task Created"); } cb(null, savedTask); } catch (err) { cb(err); } }
為什么?因為在 Dima 的模式中,if (err) 實際上處理了兩個業務:一是捕捉會引起中斷的 err ,并將其轉換為錯誤消息,二是通過 return 中斷業務過程。所以當 err 轉換為錯誤消息這一過程不再需要的時候,這種捕捉中斷再重新引起中斷的處理就顯得多余了。
繼續改進 用函數表達式改善 try-catch 邏輯當然還有改進的空間,比如 try {} 塊中的代碼比較長,會造成閱讀不太方便,try-catch 的邏輯有被“切斷”的感覺。這種情況下可以使用函數表達式來改善
async function asyncTask(cb) { async function process() { const user = await UserModel.findById(1); const savedTask = await TaskModel({ userId: user.id, name: "Demo Task" }); if (user.notificationsEnabled) { await NotificationService.sendNotification(user.id, "Task Created"); } return savedTask; } try { cb(null, await process()); } catch (err) { cb(err); } }
如果對錯誤的處理代碼比較長,也可以寫成多帶帶的函數表達式。
如果過程中每一步的錯誤處理邏輯不同怎么辦如果發生錯誤,不再轉換為錯誤消息,而是特定的錯誤處理邏輯,怎么辦?
思考一下,我們用字符串來表示錯誤消息,以后可以通過 console.log() 來處理處理。而邏輯,最適合的表示當然是函數表達式,最終可以通過調用來進行統一處理
async function asyncTask(cb) { async function process() { const user = await UserModel.findById(1) .catch(err => Promise.reject(() => { // deal with error on looking for the user return "No user found"; })); const savedTask = await TaskModel({ userId: user.id, name: "Demo Task" }) .catch(err => Promise.reject(() => { // making model error // deal with it return err === 1 ? "Error occurred while saving task" : "Error occurred while making model"; })); if (user.notificationsEnabled) { await NotificationService.sendNotification(user.id, "Task Created") .catch(err => Promise.reject(() => { // just print a message logger.log(err); return "Error while sending notification"; })); } return savedTask; } try { cb(null, await process()); } catch (func) { cb(func()); } }甚至還可以處理更復雜的情況
現在應該都知道 .catch(err => Promise.reject(xx)),這里的 xx 就是 try-catch 的 catch 塊捕捉到的對象,所以如果不同的業務 reject 出來不同的對象,比如有些是函數(表示錯誤處理邏輯),有些是字符串(表示錯誤消息),有些是數字(表示錯誤代碼)——其實只需要改 catch 塊就行
try { // ... } catch(something) { switch (typeof something) { case "string": // show message something break; case "function": something(); break; case "number": // look up something as code // and show correlative message break; default: // deal with unknown error } }小結
我沒有批判 Dima 的錯誤處理方式,這個錯誤處理方式很好,很符合 Node 錯誤處理的風格,也一定會受到很多人的喜愛。由于 Dima 的錯誤處理方式給帶靈感,同時也讓我再次審視了一直比較喜歡的 try-catch 方式。
用什么方式取決于適用場景、團隊約定和個人喜好等多種因素,在不同的情況下需要采用不同的處理方式,并不是說哪一種就一定好于另一種——合適的才是最好的!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92014.html
摘要:和異步處理調用訪問數據采用的方式,這是一個異步過程,異步過程最基本的處理方式是事件或回調,其實這兩種處理方式實現原理差不多,都需要在調用異步過程的時候傳入一個在異步過程結束的時候調用的接口。 Ajax 和異步處理 調用 API 訪問數據采用的 Ajax 方式,這是一個異步過程,異步過程最基本的處理方式是事件或回調,其實這兩種處理方式實現原理差不多,都需要在調用異步過程的時候傳入一個在異...
摘要:這只是一個更優雅的得到值的語句,它比更加容易閱讀和書寫。總結放在一個函數前的有兩個作用使函數總是返回一個允許在這其中使用前面的關鍵字能夠使等待,直到處理結束。 Async/await 寫在前面 渣渣新人的首篇外文文章翻譯!!存在錯誤可能會很多,如有錯誤,煩請各位大大指正出來,感謝! 本篇為翻譯!本篇為翻譯!本篇為翻譯! 原文文章地址:https://javascript.info/a...
摘要:所以異步編程對語言太重要。異步編程我們就以用戶注冊這個特別常見的場景為例,講講異步編程。這種層層嵌套被稱為回調地獄。相比回調函數而言,代碼可讀性更高,代碼的執行順序一目了然。函數內部語句返回的值,會成為方法回調函數的參數。 單線程是Javascript語言最本質的特性之一,Javascript引擎在運行js代碼的時候,同一個時間只能執行單個任務。 這種模式的好處是實現起來比較簡單,執行...
摘要:異步編程解決方案筆記最近讀了樸靈老師的深入淺出中異步編程一章,并參考了一些有趣的文章。另外回調函數中的也失去了意義,這會使我們的程序必須依賴于副作用。 JavaScript 異步編程解決方案筆記 最近讀了樸靈老師的《深入淺出NodeJS》中《異步編程》一章,并參考了一些有趣的文章。在此做個筆記,記錄并鞏固學到的知識。 JavaScript異步編程的兩個核心難點 異步I/O、事件驅動使得...
摘要:等待的基本語法該關鍵字的的意思就是讓編譯器等待并返回結果。這里并不會占用資源,因為引擎可以同時執行其他任務其他腳本或處理事件。接下來,我們寫一個火箭發射場景的小例子不是真的發射火箭 本文由云+社區發表 本篇文章,小編將和大家一起學習異步編程的未來——async/await,它會打破你對上篇文章Promise的認知,竟然異步代碼還能這么寫! 但是別太得意,你需要深入理解Promise后,...
閱讀 3817·2021-11-18 13:19
閱讀 1169·2021-10-11 10:58
閱讀 3278·2019-08-29 16:39
閱讀 3130·2019-08-26 12:08
閱讀 2026·2019-08-26 11:33
閱讀 2453·2019-08-23 18:30
閱讀 1298·2019-08-23 18:21
閱讀 2515·2019-08-23 18:18