Callback Promise Generator Async-Await 和異常處理的演進(jìn)

根據(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 Awaitgenerator 有著莫大的關(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í)之士肃拜!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末矗晃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子猛蔽,更是在濱河造成了極大的恐慌戚嗅,老刑警劉巖枢舶,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懦胞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡凉泄,警方通過查閱死者的電腦和手機(jī)躏尉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)后众,“玉大人,你說(shuō)我怎么就攤上這事蒂誉。” “怎么了右锨?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我讥电,道長(zhǎng),這世上最難降的妖魔是什么恩敌? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任横媚,我火速辦了婚禮纠炮,結(jié)果婚禮上灯蝴,老公的妹妹穿的比我還像新娘抗碰。我一直安慰自己绽乔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布折砸。 她就那樣靜靜地躺著,像睡著了一般睦授。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上去枷,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音删顶,去河邊找鬼。 笑死逗余,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的录粱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼菜职,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了旗闽?” 一聲冷哼從身側(cè)響起蜜另,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤愁茁,失蹤者是張志新(化名)和其女友劉穎亭病,沒想到半個(gè)月后鹅很,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罪帖,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年整袁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坐昙。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炸客,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情痹仙,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布开仰,位于F島的核電站,受9級(jí)特大地震影響众弓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谓娃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望傻粘。 院中可真熱鬧,春花似錦弦悉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至昧甘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間战得,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工浇冰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人聋亡。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像坡倔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子罪塔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 簡(jiǎn)單介紹下這幾個(gè)的關(guān)系為方便起見 用以下代碼為例簡(jiǎn)單介紹下這幾個(gè)東西的關(guān)系, async 在函數(shù)聲明前使用asyn...
    _我和你一樣閱讀 21,232評(píng)論 1 24
  • 異步編程對(duì)JavaScript語(yǔ)言太重要墓拜。Javascript語(yǔ)言的執(zhí)行環(huán)境是“單線程”的请契,如果沒有異步編程咳榜,根本...
    呼呼哥閱讀 7,313評(píng)論 5 22
  • javascript的運(yùn)行機(jī)制是單線程處理腮考,即只有上一個(gè)任務(wù)完成后雇毫,才會(huì)執(zhí)行下一個(gè)任務(wù)踩蔚,這種機(jī)制也被稱為“同步”棚放。...
    我是xy閱讀 3,901評(píng)論 1 6
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持馅闽,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券馍迄,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 8,683評(píng)論 0 29
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持攀圈,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠赘来,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 6,380評(píng)論 9 19