根據(jù)筆者的項(xiàng)目經(jīng)驗(yàn)姐军,本文講解了從函數(shù)回調(diào)器净,到 es7
規(guī)范的異常處理方式。異常處理的優(yōu)雅性隨著規(guī)范的進(jìn)步越來(lái)越高萍摊,不要害怕使用 try catch
挤茄,不能回避異常處理。
我們需要一個(gè)健全的架構(gòu)捕獲所有同步冰木、異步的異常穷劈。業(yè)務(wù)方不處理異常時(shí),中斷函數(shù)執(zhí)行并啟用默認(rèn)處理踊沸,業(yè)務(wù)方也可以隨時(shí)捕獲異常自己處理歇终。
優(yōu)雅的異常處理方式就像冒泡事件,任何元素可以自由攔截逼龟,也可以放任不管交給頂層處理评凝。
文字講解僅是背景知識(shí)介紹,不包含對(duì)代碼塊的完整解讀腺律,不要忽略代碼塊的閱讀奕短。
1. 回調(diào)
如果在回調(diào)函數(shù)中直接處理了異常,是最不明智的選擇匀钧,因?yàn)闃I(yè)務(wù)方完全失去了對(duì)異常的控制能力翎碑。
下方的函數(shù) 請(qǐng)求處理
不但永遠(yuǎn)不會(huì)執(zhí)行,還無(wú)法在異常時(shí)做額外的處理之斯,也無(wú)法阻止異常產(chǎn)生時(shí)笨拙的 console.log('請(qǐng)求失敗')
行為日杈。
function fetch(callback) {
setTimeout(() => {
console.log('請(qǐng)求失敗')
})
}
fetch(() => {
console.log('請(qǐng)求處理') // 永遠(yuǎn)不會(huì)執(zhí)行
})
2. 回調(diào),無(wú)法捕獲的異常
回調(diào)函數(shù)有同步和異步之分,區(qū)別在于對(duì)方執(zhí)行回調(diào)函數(shù)的時(shí)機(jī)达椰,異常一般出現(xiàn)在請(qǐng)求翰蠢、數(shù)據(jù)庫(kù)連接等操作中,這些操作大多是異步的啰劲。
異步回調(diào)中梁沧,回調(diào)函數(shù)的執(zhí)行棧與原函數(shù)分離開,導(dǎo)致外部無(wú)法抓住異常蝇裤。
從下文開始廷支,我們約定用
setTimeout
模擬異步操作
function fetch(callback) {
setTimeout(() => {
throw Error('請(qǐng)求失敗')
})
}
try {
fetch(() => {
console.log('請(qǐng)求處理') // 永遠(yuǎn)不會(huì)執(zhí)行
})
} catch (error) {
console.log('觸發(fā)異常', error) // 永遠(yuǎn)不會(huì)執(zhí)行
}
// 程序崩潰
// Uncaught Error: 請(qǐng)求失敗
3. 回調(diào),不可控的異常
我們變得謹(jǐn)慎栓辜,不敢再隨意拋出異常恋拍,這已經(jīng)違背了異常處理的基本原則。
雖然使用了 error-first
約定藕甩,使異呈└遥看起來(lái)變得可處理,但業(yè)務(wù)方依然沒有對(duì)異常的控制權(quán)狭莱,是否調(diào)用錯(cuò)誤處理取決于回調(diào)函數(shù)是否執(zhí)行僵娃,我們無(wú)法知道調(diào)用的函數(shù)是否可靠。
更糟糕的問題是腋妙,業(yè)務(wù)方必須處理異常默怨,否則程序掛掉就會(huì)什么都不做,這對(duì)大部分不用特殊處理異常的場(chǎng)景造成了很大的精神負(fù)擔(dān)骤素。
function fetch(handleError, callback) {
setTimeout(() => {
handleError('請(qǐng)求失敗')
})
}
fetch(() => {
console.log('失敗處理') // 失敗處理
}, error => {
console.log('請(qǐng)求處理') // 永遠(yuǎn)不會(huì)執(zhí)行
})
番外 Promise 基礎(chǔ)
Promise
是一個(gè)承諾匙睹,只可能是成功、失敗济竹、無(wú)響應(yīng)三種情況之一痕檬,一旦決策,無(wú)法修改結(jié)果送浊。
Promise
不屬于流程控制谆棺,但流程控制可以用多個(gè) Promise
組合實(shí)現(xiàn),因此它的職責(zé)很單一罕袋,就是對(duì)一個(gè)決議的承諾改淑。
resolve
表明通過的決議,reject
表明拒絕的決議浴讯,如果決議通過朵夏,then
函數(shù)的第一個(gè)回調(diào)會(huì)立即插入 microtask
隊(duì)列,異步立即執(zhí)行榆纽。
簡(jiǎn)單補(bǔ)充下事件循環(huán)的知識(shí)仰猖,js 事件循環(huán)分為 macrotask 和 microtask捏肢。
microtask 會(huì)被插入到每一個(gè) macrotask 的尾部,所以 microtask 總會(huì)優(yōu)先執(zhí)行饥侵,哪怕 macrotask 因?yàn)?js 進(jìn)程繁忙被 hung 住鸵赫。
比如setTimeout
setInterval
會(huì)插入到 macrotask 中。
const promiseA = new Promise((resolve, reject) => {
resolve('ok')
})
promiseA.then(result => {
console.log(result) // ok
})
如果決議結(jié)果是決絕躏升,那么 then
函數(shù)的第二個(gè)回調(diào)會(huì)立即插入 microtask
隊(duì)列辩棒。
const promiseB = new Promise((resolve, reject) => {
reject('no')
})
promiseB.then(result => {
console.log(result) // 永遠(yuǎn)不會(huì)執(zhí)行
}, error => {
console.log(error) // no
})
如果一直不決議,此 promise
將處于 pending
狀態(tài)膨疏。
const promiseC = new Promise((resolve, reject) => {
// nothing
})
promiseC.then(result => {
console.log(result) // 永遠(yuǎn)不會(huì)執(zhí)行
}, error => {
console.log(error) // 永遠(yuǎn)不會(huì)執(zhí)行
})
未捕獲的 reject
會(huì)傳到末尾一睁,通過 catch
接住
const promiseD = new Promise((resolve, reject) => {
reject('no')
})
promiseD.then(result => {
console.log(result) // 永遠(yuǎn)不會(huì)執(zhí)行
}).catch(error => {
console.log(error) // no
})
resolve
決議會(huì)被自動(dòng)展開(reject
不會(huì))
const promiseE = new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
resolve('ok')
})
})
promiseE.then(result => {
console.log(result) // ok
})
鏈?zhǔn)搅鳎?code>then 會(huì)返回一個(gè)新的 Promise
,其狀態(tài)取決于 then
的返回值佃却。
const promiseF = new Promise((resolve, reject) => {
resolve('ok')
})
promiseF.then(result => {
return Promise.reject('error1')
}).then(result => {
console.log(result) // 永遠(yuǎn)不會(huì)執(zhí)行
return Promise.resolve('ok1') // 永遠(yuǎn)不會(huì)執(zhí)行
}).then(result => {
console.log(result) // 永遠(yuǎn)不會(huì)執(zhí)行
}).catch(error => {
console.log(error) // error1
})
4 Promise 異常處理
不僅是 reject
者吁,拋出的異常也會(huì)被作為拒絕狀態(tài)被 Promise
捕獲。
function fetch(callback) {
return new Promise((resolve, reject) => {
throw Error('用戶不存在')
})
}
fetch().then(result => {
console.log('請(qǐng)求處理', result) // 永遠(yuǎn)不會(huì)執(zhí)行
}).catch(error => {
console.log('請(qǐng)求處理異常', error) // 請(qǐng)求處理異常 用戶不存在
})
5 Promise 無(wú)法捕獲的異常
但是饲帅,永遠(yuǎn)不要在 macrotask
隊(duì)列中拋出異常复凳,因?yàn)?macrotask
隊(duì)列脫離了運(yùn)行上下文環(huán)境,異常無(wú)法被當(dāng)前作用域捕獲灶泵。
function fetch(callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
throw Error('用戶不存在')
})
})
}
fetch().then(result => {
console.log('請(qǐng)求處理', result) // 永遠(yuǎn)不會(huì)執(zhí)行
}).catch(error => {
console.log('請(qǐng)求處理異常', error) // 永遠(yuǎn)不會(huì)執(zhí)行
})
// 程序崩潰
// Uncaught Error: 用戶不存在
不過 microtask
中拋出的異秤耍可以被捕獲,說(shuō)明 microtask
隊(duì)列并沒有離開當(dāng)前作用域丘逸,我們通過以下例子來(lái)證明:
Promise.resolve(true).then((resolve, reject)=> {
throw Error('microtask 中的異常')
}).catch(error => {
console.log('捕獲異常', error) // 捕獲異常 Error: microtask 中的異常
})
至此单鹿,Promise
的異常處理有了比較清晰的答案掀宋,只要注意在 macrotask
級(jí)別回調(diào)中使用 reject
深纲,就沒有抓不住的異常。
6 Promise 異常追問
如果第三方函數(shù)在 macrotask
回調(diào)中以 throw Error
的方式拋出異常怎么辦劲妙?
function thirdFunction() {
setTimeout(() => {
throw Error('就是任性')
})
}
Promise.resolve(true).then((resolve, reject) => {
thirdFunction()
}).catch(error => {
console.log('捕獲異常', error)
})
// 程序崩潰
// Uncaught Error: 就是任性
值得欣慰的是湃鹊,由于不在同一個(gè)調(diào)用棧,雖然這個(gè)異常無(wú)法被捕獲镣奋,但也不會(huì)影響當(dāng)前調(diào)用棧的執(zhí)行币呵。
我們必須正視這個(gè)問題,唯一的解決辦法侨颈,是第三方函數(shù)不要做這種傻事余赢,一定要在 macrotask
拋出異常的話,請(qǐng)改為 reject
的方式哈垢。
function thirdFunction() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('收斂一些')
})
})
}
Promise.resolve(true).then((resolve, reject) => {
return thirdFunction()
}).catch(error => {
console.log('捕獲異常', error) // 捕獲異常 收斂一些
})
請(qǐng)注意妻柒,如果 return thirdFunction()
這行缺少了 return
的話,依然無(wú)法抓住這個(gè)錯(cuò)誤耘分,這是因?yàn)闆]有將對(duì)方返回的 Promise
傳遞下去举塔,錯(cuò)誤也不會(huì)繼續(xù)傳遞绑警。
我們發(fā)現(xiàn),這樣還不是完美的辦法央渣,不但容易忘記 return
计盒,而且當(dāng)同時(shí)含有多個(gè)第三方函數(shù)時(shí),處理方式不太優(yōu)雅:
function thirdFunction() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('收斂一些')
})
})
}
Promise.resolve(true).then((resolve, reject) => {
return thirdFunction().then(() => {
return thirdFunction()
}).then(() => {
return thirdFunction()
}).then(() => {
})
}).catch(error => {
console.log('捕獲異常', error)
})
是的芽丹,我們還有更好的處理方式北启。
番外 Generator 基礎(chǔ)
generator
是更為優(yōu)雅的流程控制方式,可以讓函數(shù)可中斷執(zhí)行:
function* generatorA() {
console.log('a')
yield
console.log('b')
}
const genA = generatorA()
genA.next() // a
genA.next() // b
yield
關(guān)鍵字后面可以包含表達(dá)式志衍,表達(dá)式會(huì)傳給 next().value
暖庄。
next()
可以傳遞參數(shù),參數(shù)作為 yield
的返回值楼肪。
這些特性足以孕育出偉大的生成器培廓,我們稍后介紹。下面是這個(gè)特性的例子:
function* generatorB(count) {
console.log(count)
const result = yield 5
console.log(result * count)
}
const genB = generatorB(2)
genB.next() // 2
const genBValue = genB.next(7).value // 14
// genBValue undefined
第一個(gè) next 是沒有參數(shù)的春叫,因?yàn)樵趫?zhí)行 generator
函數(shù)時(shí)肩钠,初始值已經(jīng)傳入,第一個(gè) next
的參數(shù)沒有任何意義暂殖,傳入也會(huì)被丟棄价匠。
const result = yield 5
這一句,返回值不是想當(dāng)然的 5
呛每。其的作用是將 5
傳遞給 genB.next()
踩窖,其值,由下一個(gè) next genB.next(7)
傳給了它晨横,所以語(yǔ)句等于 const result = 7
洋腮。
最后一個(gè) genBValue
,是最后一個(gè) next
的返回值手形,這個(gè)值啥供,就是函數(shù)的 return
值,顯然為 undefined
库糠。
我們回到這個(gè)語(yǔ)句:
const result = yield 5
如果返回值是 5伙狐,是不是就清晰了許多?是的瞬欧,這種語(yǔ)法就是 await
贷屎。所以 Async Await
與 generator
有著莫大的關(guān)聯(lián),橋梁就是 生成器艘虎,我們稍后介紹 生成器唉侄。
番外 Async Await
如果認(rèn)為 Generator
不太好理解,那 Async Await
絕對(duì)是救命稻草顷帖,我們看看它們的特征:
const timeOut = (time = 0) => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(time + 200)
}, time)
})
async function main() {
const result1 = await timeOut(200)
console.log(result1) // 400
const result2 = await timeOut(result1)
console.log(result2) // 600
const result3 = await timeOut(result2)
console.log(result3) // 800
}
main()
所見即所得美旧,await
后面的表達(dá)式被執(zhí)行渤滞,表達(dá)式的返回值被返回給了 await
執(zhí)行處。
但是程序是怎么暫停的呢榴嗅?只有 generator
可以暫停程序妄呕。那么等等,回顧一下 generator
的特性嗽测,我們發(fā)現(xiàn)它也可以達(dá)到這種效果绪励。
番外 async await 是 generator 的語(yǔ)法糖
終于可以介紹 生成器 了!它可以魔法般將下面的 generator
執(zhí)行成為 await
的效果唠粥。
function* main() {
const result1 = yield timeOut(200)
console.log(result1)
const result2 = yield timeOut(result1)
console.log(result2)
const result3 = yield timeOut(result2)
console.log(result3)
}
下面的代碼就是生成器了疏魏,生成器并不神秘,它只有一個(gè)目的晤愧,就是:
所見即所得大莫,
yield
后面的表達(dá)式被執(zhí)行,表達(dá)式的返回值被返回給了yield
執(zhí)行處官份。
達(dá)到這個(gè)目標(biāo)不難只厘,達(dá)到了就完成了 await
的功能,就是這么神奇舅巷。
function step(generator) {
const gen = generator()
// 由于其傳值羔味,返回步驟交錯(cuò)的特性,記錄上一次 yield 傳過來(lái)的值钠右,在下一個(gè) next 返回過去
let lastValue
// 包裹為 Promise赋元,并執(zhí)行表達(dá)式
return () => Promise.resolve(gen.next(lastValue).value).then(value => {
lastValue = value
return lastValue
})
}
利用生成器,模擬出 await
的執(zhí)行效果:
const run = step(main)
function recursive(promise) {
promise().then(result => {
if (result) {
recursive(promise)
}
})
}
recursive(run)
// 400
// 600
// 800
可以看出飒房,await
的執(zhí)行次數(shù)由程序自動(dòng)控制搁凸,而回退到 generator
模擬,需要根據(jù)條件判斷是否已經(jīng)將函數(shù)執(zhí)行完畢情屹。
7 Async Await 異常
不論是同步坪仇、異步的異常杂腰,await
都不會(huì)自動(dòng)捕獲垃你,但好處是可以自動(dòng)中斷函數(shù),我們大可放心編寫業(yè)務(wù)邏輯喂很,而不用擔(dān)心異步異常后會(huì)被執(zhí)行引發(fā)雪崩:
function fetch(callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject()
})
})
}
async function main() {
const result = await fetch()
console.log('請(qǐng)求處理', result) // 永遠(yuǎn)不會(huì)執(zhí)行
}
main()
8 Async Await 捕獲異常
我們使用 try catch
捕獲異常惜颇。
認(rèn)真閱讀 Generator
番外篇的話,就會(huì)理解為什么此時(shí)異步的異成倮保可以通過 try catch
來(lái)捕獲凌摄。
因?yàn)榇藭r(shí)的異步其實(shí)在一個(gè)作用域中,通過 generator
控制執(zhí)行順序漓帅,所以可以將異步看做同步的代碼去編寫锨亏,包括使用 try catch
捕獲異常痴怨。
function fetch(callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('no')
})
})
}
async function main() {
try {
const result = await fetch()
console.log('請(qǐng)求處理', result) // 永遠(yuǎn)不會(huì)執(zhí)行
} catch (error) {
console.log('異常', error) // 異常 no
}
}
main()
9 Async Await 無(wú)法捕獲的異常
和第五章 Promise 無(wú)法捕獲的異常 一樣,這也是 await
的軟肋器予,不過任然可以通過第六章的方案解決:
function thirdFunction() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('收斂一些')
})
})
}
async function main() {
try {
const result = await thirdFunction()
console.log('請(qǐng)求處理', result) // 永遠(yuǎn)不會(huì)執(zhí)行
} catch (error) {
console.log('異常', error) // 異常 收斂一些
}
}
main()
現(xiàn)在解答第六章尾部的問題浪藻,為什么 await
是更加優(yōu)雅的方案:
async function main() {
try {
const result1 = await secondFunction() // 如果不拋出異常,后續(xù)繼續(xù)執(zhí)行
const result2 = await thirdFunction() // 拋出異常
const result3 = await thirdFunction() // 永遠(yuǎn)不會(huì)執(zhí)行
console.log('請(qǐng)求處理', result) // 永遠(yuǎn)不會(huì)執(zhí)行
} catch (error) {
console.log('異常', error) // 異常 收斂一些
}
}
main()
10 業(yè)務(wù)場(chǎng)景
在如今 action
概念成為標(biāo)配的時(shí)代乾翔,我們大可以將所有異常處理收斂到 action
中爱葵。
我們以如下業(yè)務(wù)代碼為例,默認(rèn)不捕獲錯(cuò)誤的話反浓,錯(cuò)誤會(huì)一直冒泡到頂層萌丈,最后拋出異常。
const successRequest = () => Promise.resolve('a')
const failRequest = () => Promise.reject('b')
class Action {
async successReuqest() {
const result = await successRequest()
console.log('successReuqest', '處理返回值', result) // successReuqest 處理返回值 a
}
async failReuqest() {
const result = await failRequest()
console.log('failReuqest', '處理返回值', result) // 永遠(yuǎn)不會(huì)執(zhí)行
}
async allReuqest() {
const result1 = await successRequest()
console.log('allReuqest', '處理返回值 success', result1) // allReuqest 處理返回值 success a
const result2 = await failRequest()
console.log('allReuqest', '處理返回值 success', result2) // 永遠(yuǎn)不會(huì)執(zhí)行
}
}
const action = new Action()
action.successReuqest()
action.failReuqest()
action.allReuqest()
// 程序崩潰
// Uncaught (in promise) b
// Uncaught (in promise) b
為了防止程序崩潰雷则,需要業(yè)務(wù)線在所有 async 函數(shù)中包裹 try catch
辆雾。
我們需要一種機(jī)制捕獲 action
最頂層的錯(cuò)誤進(jìn)行統(tǒng)一處理。
為了補(bǔ)充前置知識(shí)月劈,我們?cè)俅芜M(jìn)入番外話題乾颁。
番外 Decorator
Decorator
中文名是裝飾器,核心功能是可以通過外部包裝的方式艺栈,直接修改類的內(nèi)部屬性英岭。
裝飾器按照裝飾的位置,分為 class decorator
method decorator
以及 property decorator
(目前標(biāo)準(zhǔn)尚未支持湿右,通過 get
set
模擬實(shí)現(xiàn))诅妹。
Class Decorator
類級(jí)別裝飾器,修飾整個(gè)類毅人,可以讀取吭狡、修改類中任何屬性和方法。
const classDecorator = (target: any) => {
const keys = Object.getOwnPropertyNames(target.prototype)
console.log('classA keys,', keys) // classA keys ["constructor", "sayName"]
}
@classDecorator
class A {
sayName() {
console.log('classA ascoders')
}
}
const a = new A()
a.sayName() // classA ascoders
Method Decorator
方法級(jí)別裝飾器丈莺,修飾某個(gè)方法划煮,和類裝飾器功能相同,但是能額外獲取當(dāng)前修飾的方法名缔俄。
為了發(fā)揮這一特點(diǎn)弛秋,我們篡改一下修飾的函數(shù)。
const methodDecorator = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
return {
get() {
return () => {
console.log('classC method override')
}
}
}
}
class C {
@methodDecorator
sayName() {
console.log('classC ascoders')
}
}
const c = new C()
c.sayName() // classC method override
Property Decorator
屬性級(jí)別裝飾器俐载,修飾某個(gè)屬性蟹略,和類裝飾器功能相同,但是能額外獲取當(dāng)前修飾的屬性名遏佣。
為了發(fā)揮這一特點(diǎn)挖炬,我們篡改一下修飾的屬性值。
const propertyDecorator = (target: any, propertyKey: string | symbol) => {
Object.defineProperty(target, propertyKey, {
get() {
return 'github'
},
set(value: any) {
return value
}
})
}
class B {
@propertyDecorator
private name = 'ascoders'
sayName() {
console.log(`classB ${this.name}`)
}
}
const b = new B()
b.sayName() // classB github
11 業(yè)務(wù)場(chǎng)景 統(tǒng)一異常捕獲
我們來(lái)編寫類級(jí)別裝飾器状婶,專門捕獲 async
函數(shù)拋出的異常:
const asyncClass = (errorHandler?: (error?: Error) => void) => (target: any) => {
Object.getOwnPropertyNames(target.prototype).forEach(key => {
const func = target.prototype[key]
target.prototype[key] = async (...args: any[]) => {
try {
await func.apply(this, args)
} catch (error) {
errorHandler && errorHandler(error)
}
}
})
return target
}
將類所有方法都用 try catch
包裹住馅巷,將異常交給業(yè)務(wù)方統(tǒng)一的 errorHandler
處理:
const successRequest = () => Promise.resolve('a')
const failRequest = () => Promise.reject('b')
const iAsyncClass = asyncClass(error => {
console.log('統(tǒng)一異常處理', error) // 統(tǒng)一異常處理 b
})
@iAsyncClass
class Action {
async successReuqest() {
const result = await successRequest()
console.log('successReuqest', '處理返回值', result)
}
async failReuqest() {
const result = await failRequest()
console.log('failReuqest', '處理返回值', result) // 永遠(yuǎn)不會(huì)執(zhí)行
}
async allReuqest() {
const result1 = await successRequest()
console.log('allReuqest', '處理返回值 success', result1)
const result2 = await failRequest()
console.log('allReuqest', '處理返回值 success', result2) // 永遠(yuǎn)不會(huì)執(zhí)行
}
}
const action = new Action()
action.successReuqest()
action.failReuqest()
action.allReuqest()
我們也可以編寫方法級(jí)別的異常處理:
const asyncMethod = (errorHandler?: (error?: Error) => void) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const func = descriptor.value
return {
get() {
return (...args: any[]) => {
return Promise.resolve(func.apply(this, args)).catch(error => {
errorHandler && errorHandler(error)
})
}
},
set(newValue: any) {
return newValue
}
}
}
業(yè)務(wù)方用法類似草姻,只是裝飾器需要放在函數(shù)上:
const successRequest = () => Promise.resolve('a')
const failRequest = () => Promise.reject('b')
const asyncAction = asyncMethod(error => {
console.log('統(tǒng)一異常處理', error) // 統(tǒng)一異常處理 b
})
class Action {
@asyncAction async successReuqest() {
const result = await successRequest()
console.log('successReuqest', '處理返回值', result)
}
@asyncAction async failReuqest() {
const result = await failRequest()
console.log('failReuqest', '處理返回值', result) // 永遠(yuǎn)不會(huì)執(zhí)行
}
@asyncAction async allReuqest() {
const result1 = await successRequest()
console.log('allReuqest', '處理返回值 success', result1)
const result2 = await failRequest()
console.log('allReuqest', '處理返回值 success', result2) // 永遠(yuǎn)不會(huì)執(zhí)行
}
}
const action = new Action()
action.successReuqest()
action.failReuqest()
action.allReuqest()
12 業(yè)務(wù)場(chǎng)景 沒有后顧之憂的主動(dòng)權(quán)
我想描述的意思是,在第 11 章這種場(chǎng)景下碴倾,業(yè)務(wù)方是不用擔(dān)心異常導(dǎo)致的 crash
逗噩,因?yàn)樗挟惓6紩?huì)在頂層統(tǒng)一捕獲,可能表現(xiàn)為彈出一個(gè)提示框跌榔,告訴用戶請(qǐng)求發(fā)送失敗异雁。
業(yè)務(wù)方也不需要判斷程序中是否存在異常僧须,而戰(zhàn)戰(zhàn)兢兢的到處 try catch
纲刀,因?yàn)槌绦蛑腥魏萎惓6紩?huì)立刻終止函數(shù)的后續(xù)執(zhí)行担平,不會(huì)再引發(fā)更惡劣的結(jié)果示绊。
像 golang 中異常處理方式,就存在這個(gè)問題
通過 err, result := func() 的方式暂论,雖然固定了第一個(gè)參數(shù)是錯(cuò)誤信息面褐,但下一行代碼免不了要以if error {...}
開頭,整個(gè)程序的業(yè)務(wù)代碼充斥著巨量的不必要錯(cuò)誤處理取胎,而大部分時(shí)候展哭,我們還要為如何處理這些錯(cuò)誤想的焦頭爛額闻蛀。
而 js 異常冒泡的方式,在前端可以用提示框兜底觉痛,nodejs端可以返回 500 錯(cuò)誤兜底,并立刻中斷后續(xù)請(qǐng)求代碼薪棒,等于在所有危險(xiǎn)代碼身后加了一層隱藏的 return
手蝎。
同時(shí)業(yè)務(wù)方也握有絕對(duì)的主動(dòng)權(quán)盗尸,比如登錄失敗后帽撑,如果賬戶不存在泼各,那么直接跳轉(zhuǎn)到注冊(cè)頁(yè)亏拉,而不是傻瓜的提示用戶帳號(hào)不存在扣蜻,可以這樣做:
async login(nickname, password) {
try {
const user = await userService.login(nickname, password)
// 跳轉(zhuǎn)到首頁(yè),登錄失敗后不會(huì)執(zhí)行到這莽使,所以不用擔(dān)心用戶看到奇怪的跳轉(zhuǎn)
} catch (error) {
if (error.no === -1) {
// 跳轉(zhuǎn)到登錄頁(yè)
} else {
throw Error(error) // 其他錯(cuò)誤不想管,把球繼續(xù)踢走
}
}
}
補(bǔ)充
在 nodejs
端芳肌,記得監(jiān)聽全局錯(cuò)誤,兜住落網(wǎng)之魚:
process.on('uncaughtException', (error: any) => {
logger.error('uncaughtException', error)
})
process.on('unhandledRejection', (error: any) => {
logger.error('unhandledRejection', error)
})
在瀏覽器端亿笤,記得監(jiān)聽 window
全局錯(cuò)誤,兜住漏網(wǎng)之魚:
window.addEventListener('unhandledrejection', (event: any) => {
logger.error('unhandledrejection', event)
})
window.addEventListener('onrejectionhandled', (event: any) => {
logger.error('onrejectionhandled', event)
})
如有錯(cuò)誤净薛,歡迎斧正,本人 github 主頁(yè):https://github.com/ascoders 希望結(jié)交有識(shí)之士肃拜!