JS異步代碼單元測試之神奇的Promise
前言
寫這篇文章的起因是在寫單元測試時,做形如下測試時
new Promise((resolve, reject) => reject(1)).then().catch(err => { console.log(err) }) async function jestTest () { await Promise.resolve().then() console.log('這個時候catch預期已經(jīng)被調(diào)用,且輸出日志') } jestTest()
無法使用await將測試代碼恰好阻塞到catch在Event Loop中被調(diào)用后的時機,從而檢測到catch的執(zhí)行,通過測試。
而使用“神奇”一詞則是因為 promsie 的鏈式調(diào)用中確實有很多默認的 handler 和值的隱含傳遞。
promise 的鏈式調(diào)用
為了不浪費大家的時間,我們先看一個例子:
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) })
如果你答出的上述代碼的輸出順序與下述相同,那么你可以跳過這篇文章:
promise1-1 then
promise2-1 then
promise1-2 then
promise1-3 then
Error: mock error 1
promise1-4 then
首先有一個前提,就是你已經(jīng)知道了,這兩個 promise 的 then 的調(diào)用是交叉入棧的(從頭三行輸出也能看出來),如果不清楚這部分內(nèi)容,可以查閱 Event Loop 的相關(guān)文章,同時需要注意的是,在文章所指明的版本中 Chrome 與 NodejsEvent Loop 機制已經(jīng)相同。
MDN 的錯誤
我們?nèi)シ喯略荆ㄎ易隽诵薷模?MDN 關(guān)于 catch 的一段描述:
Basically, a promise chain stops if there's an exception, looking down the chain for catch handlers instead.
鏈式調(diào)用在發(fā)生異常時會停止,在鏈上查找 catch 語句來執(zhí)行。
我最初的誤解與此相同,誤以為 catch 會直接抓到第一個throw Error,即Error會在promise1-2之后輸出,即promise2-2所在的then并不會被加入調(diào)用棧。
而通過觀察實際的輸出結(jié)果發(fā)現(xiàn)并非如此,那么可以說明 MDN 解釋的字面意思應該是錯的,鏈式調(diào)用并沒有停止,而是執(zhí)行了我們沒看到的東西。
鏈式的默認處理
這時我們需要知道then的一個默認處理,同樣直接引用 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 缺少了對應狀態(tài)處理的回調(diào),那么 then 會自動生成一個接受此 promise 狀態(tài)的 promise,即 then 會返回一個狀態(tài)引用相同的 promsie,交給后續(xù)的調(diào)用。
那么上述代碼中的第二個 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') // 注意這個 onRejected }, (err) => { return Promise.reject(err) }) .catch(err => { console.log(err) })
也就是說在輸出結(jié)果的promise1-2和promise1-3之間是執(zhí)行了promise2-2所在的then的,也就是說鏈式調(diào)用并沒有直接停止,promise2-2所在的then還是被加入了調(diào)用棧。而catch并不是直接catch的第一個then拋出的錯誤,而是這個隱藏的onRejected返回的同樣狀態(tài)的promise。
簡寫
同理我們需要知道的是,catch(onRejected)是then(undefined, onRejected)的簡寫,即就算調(diào)用鏈的前置調(diào)用沒有發(fā)生錯誤,catch也是會進入調(diào)用棧而非直接跳過的。
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('其實我是 promise2-3 then') })
async await
首先需要注意的是在文章指明的 NodeJs 和 Chrome 版本中,f(await promise)完全等同于promise.then(f)。
當然,討論promise的時候,我們也不能拋開async await。雖然兩者在 promise 狀態(tài)為 onResolve 時處理邏輯相同,但錯誤處理的執(zhí)行邏輯并不一樣,在async await中發(fā)生錯誤時,才是真正的直接跳過后續(xù)await的執(zhí)行
const promiseReject = new Promise((resolve, reject) => { reject(new Error('錯誤')) }) 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異步代碼單元測試之神奇的Promise的詳細內(nèi)容,更多關(guān)于JS異步代碼之Promise的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
無閃爍更新網(wǎng)頁內(nèi)容JS實現(xiàn)
這篇文章主要介紹了無閃爍更新網(wǎng)頁內(nèi)容JS實現(xiàn),有需要的朋友可以參考一下2013-12-12JavaScript高級程序設(shè)計 讀書筆記之八 Function類及閉包
Function類及閉包,學習js的朋友可以參考下2012-02-02JavaScript 中文轉(zhuǎn)拼音實現(xiàn)代碼 有些bug
在做項目時候遇到一個小小的顯示客戶部門名稱(拼音)的業(yè)務(wù),就是在部門名稱下有相應的拼音,而在現(xiàn)有的數(shù)據(jù)庫中沒有相應字段,并且部門數(shù)量比較多,添加起來比較費時,就想能否在js中實現(xiàn),在頁面中處理。2010-03-03GreyBox技術(shù)總結(jié)(轉(zhuǎn))
GreyBox是一個遮罩層的組件也稱模式窗口或模態(tài)窗口(所謂模態(tài)窗口,就是指除非采取有效的關(guān)閉手段,用戶的鼠標焦點或者輸入光標將一直停留在其上的窗口),它運行以后可以產(chǎn)生不錯的界面。2010-11-11