前言
const p = new Promise((resolve, reject) => {
console.log('A')
setTimeout(() => {
console.log('B')
resolve('C')
})
})
p.then(res => {
console.log(res)
})
// A B C D
盡管工作中用了無(wú)數(shù)次Promise async await,但是在寫(xiě)下這篇文章之前,卻不知道Promise背后發(fā)生了些什么,我一直以為的邏輯是先等待Promise構(gòu)造方法中的異步函數(shù)完成后,再調(diào)用then方法執(zhí)行其中的函數(shù)。然而事情并沒(méi)有這么簡(jiǎn)單,這篇文章將以深入淺出的方式理解Promise背后究竟發(fā)生了什么
構(gòu)造一個(gè)Promise
按照Promise/A+規(guī)范,一個(gè)Promise應(yīng)該包含以下數(shù)據(jù)結(jié)構(gòu)
interface IPromise {
status: STATUS // 表明當(dāng)前Promise的狀態(tài),不可逆,在進(jìn)行then添加方法時(shí),會(huì)根據(jù)這個(gè)狀態(tài)做出不同的處理
value: any // 異步函數(shù)執(zhí)行成功后返回的值
reason: any // 異步函數(shù)執(zhí)行失敗后返回的值
onResolvedCallbacks: Function[] // 保存then方法添加的成功后執(zhí)行函數(shù)
onRejectCallbacks: Function[] // 保存then方法添加的失敗后的執(zhí)行函數(shù)
}
enum STATUS {
PENDING,
FULFILLED,
REJECTED
}
接著動(dòng)手實(shí)現(xiàn)一個(gè)Promise,因?yàn)樵赥S環(huán)境中Promise已經(jīng)有了,為了避免和已有的沖突,我把自己構(gòu)造的對(duì)象命名為MyPromise
class MyPromise {
private status: STATUS
private value: any
private reason: any
private onResolvedCallBacks: Function [] = []
private onRejectCallBacks: Function [] = []
constructor(executor: Function) {
const self = this
function resolve(value: any) {
// 改變當(dāng)前Promise的狀態(tài)
if (p.status === STATUS.PENDING) {
p.stats = STATUS.FULFILLED
p.value = value
p.onResolveCallbacks.forEach(fun => {
fun(p.value)
})
}
}
function reject(reason: any) {
if (p.status === STATUS.PENDING) {
p.status = STATUS.REJECTED
p.reason = reason
p.onRejectCallbacks.forEach(fun => fun(reason))
}
}
}
public then(onFulfilled: Function, onReject?: Function) {
this.onResolveCallbacks.push(onFulfilled)
if (onReject) {
this.onRjectCallback.push(onRject)
return
}
return this
}
}
這是一個(gè)最最基本的Promise實(shí)現(xiàn),寫(xiě)到這里我們?cè)囍鴱拇a中了解下Promise究竟干了些什么。
我們知道JS是異步非阻塞單線程的語(yǔ)言,遇到異步任務(wù)時(shí),將會(huì)向事件隊(duì)列添加一個(gè)函數(shù),直到異步任務(wù)完成時(shí),線程再執(zhí)行這個(gè)函數(shù),基于此,在JS中很多地方用到了訂閱者模式。
Promise正好是一個(gè)訂閱者模式的實(shí)現(xiàn)executor就是我們添加的訂閱的數(shù)據(jù)源,我們向這個(gè)源注冊(cè)了兩個(gè)鉤子resolve, reject,分別在異步事件的成功和失敗時(shí)執(zhí)行,相當(dāng)于訂閱者的notify方法。
而then方法則是向訂閱者注冊(cè)事件。這樣就能初步理解Promise干了什么。
resolve,reject方法的改進(jìn)
按照Promise預(yù)期的設(shè)計(jì),then方法時(shí)同步的向Promise的待處理隊(duì)列添加函數(shù),而executor函數(shù)則是異步的執(zhí)行一個(gè)函數(shù),再調(diào)用其中的resolve或者reject方法,也就是說(shuō)then一定先于executor執(zhí)行。上面的代碼中如果executor是一個(gè)同步的方法,那么新建這個(gè)MyPromise實(shí)例時(shí),resolve就已經(jīng)被調(diào)用了,導(dǎo)致then添加的方法無(wú)法執(zhí)行。所以我們需要做出一定的處理,保證resolve之前,已經(jīng)注冊(cè)了事件處理函數(shù)
function reject (value: any) {
// 調(diào)用setImmediate方法,保證resolve一定會(huì)在通過(guò)then同步的注冊(cè)的方法后調(diào)用
// setImmediate將會(huì)把回調(diào)中的函數(shù)加入到下一個(gè)task,優(yōu)先級(jí)要比setTimeout高
// JS中的Promise.resolve方法時(shí)將回調(diào)中的函數(shù)加入到當(dāng)前的microtask隊(duì)列,優(yōu)先級(jí)要比前者高
setImmediate(() => {
if (p.status === STATUS.PENDING) {
p.stats = STATUS.FULFILLED
p.value = value
p.onResolveCallbacks.forEach(fun => {
fun(p.value)
})
}
})
}
then方法中添加Promise的鏈?zhǔn)秸{(diào)用
之前的MyPromise通過(guò)then方法注冊(cè)事件后,雖然返回了this能夠進(jìn)行鏈?zhǔn)秸{(diào)用,但是如果注冊(cè)的事件返回的是Promise,包含異步的事件則會(huì)出錯(cuò)。針對(duì)這種狀況我們需要進(jìn)行特殊的處理
public then (onFulfilled: Function, onReject?: Function) {
// 包裝一個(gè)Promise
let promise2: MyPromise
// 保存當(dāng)前this——外部的Promise
const self: MyPromise = this
// 如果當(dāng)前異步函數(shù)執(zhí)行成功,得到了值
if (this.status === STATUS.FULFILLED) {
// 聲明一個(gè)新的 promise2
promise2 = new MyPromise((resovle, reject) => {
setImmediate(() => {
try {
// then添加的方法返回一個(gè)promise
let res = onFulfilled(self.value)
resolvePromise(promise2, res, resovle, reject)
} catch (error) {
reject(error)
}
})
})
}
if (this.status === STATUS.PENDING) {
promise2 = new MyPromise((reslove, reject) => {
self.onResolveCallbacks.push(value => {
try {
let res = onFulfilled(value)
resolvePromise(promise2, res, reslove, reject)
} catch (error) {
reject(error)
}
})
self.onRejectCallbacks.push(reason => {
try {
let res = onReject(reason)
resolvePromise(promise2, res, reslove, reject)
} catch (error) {
reject(error)
}
})
})
}
function resolvePromise (promise: MyPromise, res: any, resolve: Function, reject: Function) {
// 暫時(shí)不清楚什么情況下會(huì)出現(xiàn)循環(huán)應(yīng)用
if (promise === res) {
return reject(new TypeError('循環(huán)引用'))
}
let then
let called
// 如果onFulfilled方法返回的res不為空,并且可能是object(可能是一個(gè)Promise或者一般對(duì)象)或者function
if (res !== null && ((typeof res === 'object' || typeof res === 'function'))) {
try {
then = res.then
// 如果res具有then屬性,并且是一個(gè)function,說(shuō)明可能是一個(gè)Promise(這里應(yīng)該用類型判斷)
if (typeof then === 'function') {
// 重復(fù)調(diào)用then方法, 直到res不再是一個(gè)Promise
then.call(res, function (res2) {
// 每次調(diào)用then方法都會(huì)返回一個(gè)新的Promise,如果當(dāng)前的Promise已經(jīng)注冊(cè)過(guò)事件了,將會(huì)直接return
if (called) return
called = true
resolvePromise(promise, res2, resolve, reject)
}, function (err) {
if (called) return
called = true
reject(err)
})
} else {
// 調(diào)用resolve
resolve(res)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
resolve(res)
}
}
return promise2
}
改進(jìn)后的then方法改進(jìn)了兩個(gè)地方
- 判斷通過(guò)then方法注冊(cè)事件時(shí)Promise的狀態(tài),一個(gè)Promise的狀態(tài)應(yīng)該是確定的不可逆的,即只能從PENDING狀態(tài)轉(zhuǎn)換為fulfilled或者reject,當(dāng)調(diào)用then方法注冊(cè)事件時(shí),如果此時(shí)這個(gè)Promise已經(jīng)不是PENDING了,將會(huì)根據(jù)現(xiàn)在的Promise類型執(zhí)行then注冊(cè)的函數(shù)
- 每次調(diào)用then方法進(jìn)行函數(shù)注冊(cè)的時(shí)候都會(huì)返回一個(gè)新的Promise,這個(gè)Promise保證了then的鏈?zhǔn)秸{(diào)用。接著我們考慮了注冊(cè)的onFulfilled函數(shù),如果這個(gè)函數(shù)返回的是一個(gè)Promise,則繼續(xù)向它注冊(cè)事件
小結(jié)
- Promise本質(zhì)就是一個(gè)發(fā)布訂閱模式,異步函數(shù)是整個(gè)模型的驅(qū)動(dòng)器,完成時(shí)調(diào)用resolve執(zhí)行成功方法,then是向該模型注冊(cè)事件
- Promise巧妙的利用發(fā)布訂閱模式,將異步事件的發(fā)生與發(fā)生之后的執(zhí)行解耦了,通過(guò)resolve鉤子觸發(fā)注冊(cè)的函數(shù),使得我們的關(guān)注點(diǎn)在then之后的方法
- Typescript用起來(lái)真是爽
這篇文章只是簡(jiǎn)單的介紹了Promise背后執(zhí)行的原理,還有Promise.all Promise.race方法沒(méi)有實(shí)現(xiàn),不過(guò)已經(jīng)不重要了,我們只需要記得Promise是一個(gè)發(fā)布訂閱模式就OK,generator和 async await的方法也沒(méi)有實(shí)現(xiàn)。不過(guò)基于此,可以大膽的猜測(cè)。通過(guò)await執(zhí)行的Promise,是將原本resolve我們注冊(cè)的函數(shù)改為了執(zhí)行await方法中的函數(shù),再把值取出來(lái)給我們調(diào)用。大抵應(yīng)該是這個(gè)原理。實(shí)現(xiàn)上需要寫(xiě)一個(gè)generator runtime這也超過(guò)大部分人的能力。因此能夠用好async await就好了。
本文的源代碼在 Github 歡迎star
本文參考自文章 確認(rèn)過(guò)眼神,你就是我的Promise~~
以上都是我瞎編的