摘要:像這種中斷式的錯(cuò)誤處理,其實(shí)正適合采用。然而注意,上面的代碼中并沒(méi)有直接使用,而是使用了自定義的錯(cuò)誤消息。所以需要對(duì)出來(lái)的進(jìn)一步處理成指定的錯(cuò)誤消息。
前不久看到 Dima Grossman 寫(xiě)的 How to write async await without try-catch blocks in Javascript??吹綐?biāo)題的時(shí)候,我感到非常好奇。我知道雖然在異步程序中可以不使用 try-catch 配合 async/await 來(lái)處理錯(cuò)誤,但是處理方式并不能與 async/await 配合得很好,所以很想知道到底有什么辦法會(huì)比 try-catch 更好用。
Dima 去除 try-catch 的方法當(dāng)然套路依舊,Dima 講到了回調(diào)地獄,Promise 鏈并最終引出了 async/await。而在處理錯(cuò)誤的時(shí)候,他并不喜歡 try-catch 的方式,所以寫(xiě)了一個(gè) to(promise) 來(lái)對(duì) Promise 進(jìn)行封裝,輔以解構(gòu)語(yǔ)法,實(shí)現(xiàn)了同步寫(xiě)法但類(lèi)似 Node 錯(cuò)誤標(biāo)準(zhǔn)的代碼。摘抄代碼如下
// to.js export default function to(promise) { return promise .then(data => { return [null, data]; }) .catch(err => [err]); }
應(yīng)用示例:
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 的辦法讓人產(chǎn)生的了熟悉的感覺(jué),Node 的回調(diào)中不是經(jīng)常都這樣寫(xiě)嗎?
(err, data) => { if (err) { // deal with error } else { // deal with data } }
所以這個(gè)方法真的很有意思。不過(guò)回過(guò)頭來(lái)想一想,這段代碼中每當(dāng)遇到錯(cuò)誤,都是將錯(cuò)誤消息通過(guò) cb() 調(diào)用推出去,同時(shí)中斷后續(xù)過(guò)程。像這種中斷式的錯(cuò)誤處理,其實(shí)正適合采用 try-catch。
使用 try-catch 改寫(xiě)上面的代碼要用 try-catch 改寫(xiě)上面的代碼,首先要去掉 to() 封裝。這樣,一旦發(fā)生錯(cuò)誤,需要使用 Promise.prototype.catch() 進(jìn)行捕捉,或者使用 try-catch 對(duì) await promise 語(yǔ)句進(jìn)行捕捉。捕捉到的,當(dāng)然是每個(gè)業(yè)務(wù)代碼里 reject 出來(lái)的 err。
然而注意,上面的代碼中并沒(méi)有直接使用 err,而是使用了自定義的錯(cuò)誤消息。所以需要對(duì) reject 出來(lái)的 err 進(jìn)一步處理成指定的錯(cuò)誤消息。當(dāng)然這難不到誰(shuí),比如
someAsync().catch(err => Promise.reject("specified message"));
然后再最外層加上 try-catch 就好。所以改寫(xiě)之后的代碼是:
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); } }
上面這段代碼,從代碼量上來(lái)說(shuō),并沒(méi)有比 Dima 的代碼減少了多少工作量,只是去掉了大量 if (err) {} 結(jié)構(gòu)。不習(xí)慣使用 try-catch 的程序員找找不到中斷點(diǎn),但習(xí)慣了 try-catch 的程序員都知道,業(yè)務(wù)過(guò)程中一旦發(fā)生錯(cuò)誤(異步代碼里指 reject),代碼就會(huì)跳到 catch 塊去處理 reject 出來(lái)的值。
但是,一般業(yè)務(wù)代碼 reject 出來(lái)的信息通常都是有用的。假如上面的每個(gè)業(yè)務(wù) reject 出來(lái)的 err 本身就是錯(cuò)誤消息,那么,用 Dima 的模式,仍然需要寫(xiě)
if (err) return cb(err);
而用 try-catch 的模式,就簡(jiǎn)單多了
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); } }
為什么?因?yàn)樵?Dima 的模式中,if (err) 實(shí)際上處理了兩個(gè)業(yè)務(wù):一是捕捉會(huì)引起中斷的 err ,并將其轉(zhuǎn)換為錯(cuò)誤消息,二是通過(guò) return 中斷業(yè)務(wù)過(guò)程。所以當(dāng) err 轉(zhuǎn)換為錯(cuò)誤消息這一過(guò)程不再需要的時(shí)候,這種捕捉中斷再重新引起中斷的處理就顯得多余了。
繼續(xù)改進(jìn) 用函數(shù)表達(dá)式改善 try-catch 邏輯當(dāng)然還有改進(jìn)的空間,比如 try {} 塊中的代碼比較長(zhǎng),會(huì)造成閱讀不太方便,try-catch 的邏輯有被“切斷”的感覺(jué)。這種情況下可以使用函數(shù)表達(dá)式來(lái)改善
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); } }
如果對(duì)錯(cuò)誤的處理代碼比較長(zhǎng),也可以寫(xiě)成多帶帶的函數(shù)表達(dá)式。
如果過(guò)程中每一步的錯(cuò)誤處理邏輯不同怎么辦如果發(fā)生錯(cuò)誤,不再轉(zhuǎn)換為錯(cuò)誤消息,而是特定的錯(cuò)誤處理邏輯,怎么辦?
思考一下,我們用字符串來(lái)表示錯(cuò)誤消息,以后可以通過(guò) console.log() 來(lái)處理處理。而邏輯,最適合的表示當(dāng)然是函數(shù)表達(dá)式,最終可以通過(guò)調(diào)用來(lái)進(jìn)行統(tǒng)一處理
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()); } }甚至還可以處理更復(fù)雜的情況
現(xiàn)在應(yīng)該都知道 .catch(err => Promise.reject(xx)),這里的 xx 就是 try-catch 的 catch 塊捕捉到的對(duì)象,所以如果不同的業(yè)務(wù) reject 出來(lái)不同的對(duì)象,比如有些是函數(shù)(表示錯(cuò)誤處理邏輯),有些是字符串(表示錯(cuò)誤消息),有些是數(shù)字(表示錯(cuò)誤代碼)——其實(shí)只需要改 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 } }小結(jié)
我沒(méi)有批判 Dima 的錯(cuò)誤處理方式,這個(gè)錯(cuò)誤處理方式很好,很符合 Node 錯(cuò)誤處理的風(fēng)格,也一定會(huì)受到很多人的喜愛(ài)。由于 Dima 的錯(cuò)誤處理方式給帶靈感,同時(shí)也讓我再次審視了一直比較喜歡的 try-catch 方式。
用什么方式取決于適用場(chǎng)景、團(tuán)隊(duì)約定和個(gè)人喜好等多種因素,在不同的情況下需要采用不同的處理方式,并不是說(shuō)哪一種就一定好于另一種——合適的才是最好的!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/92014.html
摘要:和異步處理調(diào)用訪問(wèn)數(shù)據(jù)采用的方式,這是一個(gè)異步過(guò)程,異步過(guò)程最基本的處理方式是事件或回調(diào),其實(shí)這兩種處理方式實(shí)現(xiàn)原理差不多,都需要在調(diào)用異步過(guò)程的時(shí)候傳入一個(gè)在異步過(guò)程結(jié)束的時(shí)候調(diào)用的接口。 Ajax 和異步處理 調(diào)用 API 訪問(wèn)數(shù)據(jù)采用的 Ajax 方式,這是一個(gè)異步過(guò)程,異步過(guò)程最基本的處理方式是事件或回調(diào),其實(shí)這兩種處理方式實(shí)現(xiàn)原理差不多,都需要在調(diào)用異步過(guò)程的時(shí)候傳入一個(gè)在異...
摘要:這只是一個(gè)更優(yōu)雅的得到值的語(yǔ)句,它比更加容易閱讀和書(shū)寫(xiě)??偨Y(jié)放在一個(gè)函數(shù)前的有兩個(gè)作用使函數(shù)總是返回一個(gè)允許在這其中使用前面的關(guān)鍵字能夠使等待,直到處理結(jié)束。 Async/await 寫(xiě)在前面 渣渣新人的首篇外文文章翻譯?。〈嬖阱e(cuò)誤可能會(huì)很多,如有錯(cuò)誤,煩請(qǐng)各位大大指正出來(lái),感謝! 本篇為翻譯!本篇為翻譯!本篇為翻譯! 原文文章地址:https://javascript.info/a...
摘要:所以異步編程對(duì)語(yǔ)言太重要。異步編程我們就以用戶注冊(cè)這個(gè)特別常見(jiàn)的場(chǎng)景為例,講講異步編程。這種層層嵌套被稱(chēng)為回調(diào)地獄。相比回調(diào)函數(shù)而言,代碼可讀性更高,代碼的執(zhí)行順序一目了然。函數(shù)內(nèi)部語(yǔ)句返回的值,會(huì)成為方法回調(diào)函數(shù)的參數(shù)。 單線程是Javascript語(yǔ)言最本質(zhì)的特性之一,Javascript引擎在運(yùn)行js代碼的時(shí)候,同一個(gè)時(shí)間只能執(zhí)行單個(gè)任務(wù)。 這種模式的好處是實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單,執(zhí)行...
摘要:異步編程解決方案筆記最近讀了樸靈老師的深入淺出中異步編程一章,并參考了一些有趣的文章。另外回調(diào)函數(shù)中的也失去了意義,這會(huì)使我們的程序必須依賴(lài)于副作用。 JavaScript 異步編程解決方案筆記 最近讀了樸靈老師的《深入淺出NodeJS》中《異步編程》一章,并參考了一些有趣的文章。在此做個(gè)筆記,記錄并鞏固學(xué)到的知識(shí)。 JavaScript異步編程的兩個(gè)核心難點(diǎn) 異步I/O、事件驅(qū)動(dòng)使得...
摘要:等待的基本語(yǔ)法該關(guān)鍵字的的意思就是讓編譯器等待并返回結(jié)果。這里并不會(huì)占用資源,因?yàn)橐婵梢酝瑫r(shí)執(zhí)行其他任務(wù)其他腳本或處理事件。接下來(lái),我們寫(xiě)一個(gè)火箭發(fā)射場(chǎng)景的小例子不是真的發(fā)射火箭 本文由云+社區(qū)發(fā)表 本篇文章,小編將和大家一起學(xué)習(xí)異步編程的未來(lái)——async/await,它會(huì)打破你對(duì)上篇文章Promise的認(rèn)知,竟然異步代碼還能這么寫(xiě)! 但是別太得意,你需要深入理解Promise后,...
閱讀 3926·2021-11-18 13:19
閱讀 1179·2021-10-11 10:58
閱讀 3291·2019-08-29 16:39
閱讀 3140·2019-08-26 12:08
閱讀 2035·2019-08-26 11:33
閱讀 2460·2019-08-23 18:30
閱讀 1308·2019-08-23 18:21
閱讀 2522·2019-08-23 18:18