JS異步代碼單元測(cè)試之神奇的Promise
前言
寫這篇文章的起因是在寫單元測(cè)試時(shí),做形如下測(cè)試時(shí)
new Promise((resolve, reject) => reject(1)).then().catch(err => {
console.log(err)
})
async function jestTest () {
await Promise.resolve().then()
console.log('這個(gè)時(shí)候catch預(yù)期已經(jīng)被調(diào)用,且輸出日志')
}
jestTest()
無(wú)法使用await將測(cè)試代碼恰好阻塞到catch在Event Loop中被調(diào)用后的時(shí)機(jī),從而檢測(cè)到catch的執(zhí)行,通過(guò)測(cè)試。
而使用“神奇”一詞則是因?yàn)?promsie 的鏈?zhǔn)秸{(diào)用中確實(shí)有很多默認(rèn)的 handler 和值的隱含傳遞。
promise 的鏈?zhǔn)秸{(diào)用
為了不浪費(fèi)大家的時(shí)間,我們先看一個(gè)例子:
Promise.resolve('promise1')
.then(res => {
console.log('promise1-1 then')
})
.then(res => {
console.log('promise1-2 then')
})
.then(res => {
console.log('promise1-3 then')
})
.then(res => {
console.log('promise1-4 then')
})
Promise.resolve('promise2')
.then(res => {
console.log('promise2-1 then')
throw new Error('mock error 1')
})
.then(res => {
console.log('promise2-2 then')
throw new Error('mock error 2')
})
.catch(err => {
console.log(err)
})
如果你答出的上述代碼的輸出順序與下述相同,那么你可以跳過(guò)這篇文章:
promise1-1 then
promise2-1 then
promise1-2 then
promise1-3 then
Error: mock error 1
promise1-4 then
首先有一個(gè)前提,就是你已經(jīng)知道了,這兩個(gè) promise 的 then 的調(diào)用是交叉入棧的(從頭三行輸出也能看出來(lái)),如果不清楚這部分內(nèi)容,可以查閱 Event Loop 的相關(guān)文章,同時(shí)需要注意的是,在文章所指明的版本中 Chrome 與 NodejsEvent Loop 機(jī)制已經(jīng)相同。
MDN 的錯(cuò)誤
我們?nèi)シ喯略荆ㄎ易隽诵薷模?MDN 關(guān)于 catch 的一段描述:
Basically, a promise chain stops if there's an exception, looking down the chain for catch handlers instead.
鏈?zhǔn)秸{(diào)用在發(fā)生異常時(shí)會(huì)停止,在鏈上查找 catch 語(yǔ)句來(lái)執(zhí)行。
我最初的誤解與此相同,誤以為 catch 會(huì)直接抓到第一個(gè)throw Error,即Error會(huì)在promise1-2之后輸出,即promise2-2所在的then并不會(huì)被加入調(diào)用棧。
而通過(guò)觀察實(shí)際的輸出結(jié)果發(fā)現(xiàn)并非如此,那么可以說(shuō)明 MDN 解釋的字面意思應(yīng)該是錯(cuò)的,鏈?zhǔn)秸{(diào)用并沒(méi)有停止,而是執(zhí)行了我們沒(méi)看到的東西。
鏈?zhǔn)降哪J(rèn)處理
這時(shí)我們需要知道then的一個(gè)默認(rèn)處理,同樣直接引用 MDN 的描述:
If the Promise that then is called on adopts a state (fulfillment or rejection) for which then has no handler, a new Promise is created with no additional handlers, simply adopting the final state of the original Promise on which then was called.
如果你的 promise 的 then 缺少了對(duì)應(yīng)狀態(tài)處理的回調(diào),那么 then 會(huì)自動(dòng)生成一個(gè)接受此 promise 狀態(tài)的 promise,即 then 會(huì)返回一個(gè)狀態(tài)引用相同的 promsie,交給后續(xù)的調(diào)用。
那么上述代碼中的第二個(gè) promise 部分就等效于
Promise.resolve('promise2')
.then(res => {
console.log('promise2-1 then')
throw new Error('mock error 1')
})
.then(res => {
console.log('promise2-2 then')
throw new Error('mock error 2')
// 注意這個(gè) onRejected
}, (err) => {
return Promise.reject(err)
})
.catch(err => {
console.log(err)
})
也就是說(shuō)在輸出結(jié)果的promise1-2和promise1-3之間是執(zhí)行了promise2-2所在的then的,也就是說(shuō)鏈?zhǔn)秸{(diào)用并沒(méi)有直接停止,promise2-2所在的then還是被加入了調(diào)用棧。而catch并不是直接catch的第一個(gè)then拋出的錯(cuò)誤,而是這個(gè)隱藏的onRejected返回的同樣狀態(tài)的promise。
簡(jiǎn)寫
同理我們需要知道的是,catch(onRejected)是then(undefined, onRejected)的簡(jiǎn)寫,即就算調(diào)用鏈的前置調(diào)用沒(méi)有發(fā)生錯(cuò)誤,catch也是會(huì)進(jìn)入調(diào)用棧而非直接跳過(guò)的。
Promise.resolve('promise1')
.then(res => {
console.log('promise1-1 then')
})
.then(res => {
console.log('promise1-2 then')
})
.then(res => {
console.log('promise1-3 then')
})
Promise.resolve('promise2')
.then(res => {
console.log('promise2-1 then')
})
.catch(err => {
console.log(err)
})
.then(res => {
console.log('其實(shí)我是 promise2-3 then')
})
async await
首先需要注意的是在文章指明的 NodeJs 和 Chrome 版本中,f(await promise)完全等同于promise.then(f)。
當(dāng)然,討論promise的時(shí)候,我們也不能拋開(kāi)async await。雖然兩者在 promise 狀態(tài)為 onResolve 時(shí)處理邏輯相同,但錯(cuò)誤處理的執(zhí)行邏輯并不一樣,在async await中發(fā)生錯(cuò)誤時(shí),才是真正的直接跳過(guò)后續(xù)await的執(zhí)行
const promiseReject = new Promise((resolve, reject) => {
reject(new Error('錯(cuò)誤'))
})
const promiseResolve1 = new Promise((resolve, reject) => {
resolve('正確')
})
const promiseResolve2 = new Promise((resolve, reject) => {
resolve('正確')
})
const promiseResolve3 = new Promise((resolve, reject) => {
resolve('正確')
})
function demo1 () {
promiseReject
.then(() => {
console.log('1-1')
})
.catch(err => {
console.log('1-2')
})
}
async function demo2 () {
try {
await promiseReject
await promiseResolve1
await promiseResolve2
await promiseResolve3
} catch (error) {
console.log('2-1')
}
}
// 2-1
// 1-2
以上就是JS異步代碼單元測(cè)試之神奇的Promise的詳細(xì)內(nèi)容,更多關(guān)于JS異步代碼之Promise的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
無(wú)閃爍更新網(wǎng)頁(yè)內(nèi)容JS實(shí)現(xiàn)
這篇文章主要介紹了無(wú)閃爍更新網(wǎng)頁(yè)內(nèi)容JS實(shí)現(xiàn),有需要的朋友可以參考一下2013-12-12
JS 用6N±1法求素?cái)?shù) 實(shí)例教程
顯然,當(dāng)N≥1時(shí),6N,6N+2,6N+3,6N+4都不是素?cái)?shù),只有形如6N+1和6N+5的自然數(shù)有可能是素?cái)?shù)。所以,除了2和3之外,所有的素?cái)?shù)都可以表示成6N±1的形式(N為自然數(shù))。2009-10-10
JavaScript高級(jí)程序設(shè)計(jì) 讀書(shū)筆記之八 Function類及閉包
Function類及閉包,學(xué)習(xí)js的朋友可以參考下2012-02-02
JS實(shí)現(xiàn)移動(dòng)端判斷上拉和下滑功能
通過(guò)手指觸屏,利用touchstart和touchend計(jì)算前后滑動(dòng)距離,判斷是上拉還是下滑。接下來(lái)通過(guò)實(shí)例代碼給大家介紹js移動(dòng)端判斷上拉和下滑功能,感興趣的朋友一起看看吧2017-08-08
JavaScript 中文轉(zhuǎn)拼音實(shí)現(xiàn)代碼 有些bug
在做項(xiàng)目時(shí)候遇到一個(gè)小小的顯示客戶部門名稱(拼音)的業(yè)務(wù),就是在部門名稱下有相應(yīng)的拼音,而在現(xiàn)有的數(shù)據(jù)庫(kù)中沒(méi)有相應(yīng)字段,并且部門數(shù)量比較多,添加起來(lái)比較費(fèi)時(shí),就想能否在js中實(shí)現(xiàn),在頁(yè)面中處理。2010-03-03
JavaScript下一版本標(biāo)準(zhǔn)ES6的Set集合使用詳解
ES6:全稱ECMAScript 6.0,是JavaScript語(yǔ)言的國(guó)際標(biāo)準(zhǔn),JavaScript是ECMAScript的實(shí)現(xiàn)。今天我們就來(lái)學(xué)習(xí)一下ES6的Set集合的使用2023-02-02
符合W3C網(wǎng)頁(yè)標(biāo)準(zhǔn)的iframe標(biāo)簽的使用方法
符合W3C網(wǎng)頁(yè)標(biāo)準(zhǔn)的iframe標(biāo)簽的使用方法...2007-07-07
GreyBox技術(shù)總結(jié)(轉(zhuǎn))
GreyBox是一個(gè)遮罩層的組件也稱模式窗口或模態(tài)窗口(所謂模態(tài)窗口,就是指除非采取有效的關(guān)閉手段,用戶的鼠標(biāo)焦點(diǎn)或者輸入光標(biāo)將一直停留在其上的窗口),它運(yùn)行以后可以產(chǎn)生不錯(cuò)的界面。2010-11-11
JavaScript引用類型Object常見(jiàn)用法實(shí)例分析
這篇文章主要介紹了JavaScript引用類型Object常見(jiàn)用法,簡(jiǎn)單描述了javascript基本數(shù)據(jù)類型,并結(jié)合實(shí)例形式分析了引用類型Object基本創(chuàng)建、賦值、訪問(wèn)屬性等基本操作技巧,需要的朋友可以參考下2018-08-08

