V8引擎的實(shí)現(xiàn)源碼:promise.js
非官方實(shí)現(xiàn),來(lái)自:Promise實(shí)現(xiàn)原理(附源碼)
注:啃官方源碼和其他原型鏈的實(shí)現(xiàn)有點(diǎn)煩躁,找了個(gè)用class語(yǔ)法糖的源碼實(shí)現(xiàn)德召,下面代碼主要是這個(gè)非官方實(shí)現(xiàn)
快速答幾個(gè)問題先:
1.
setTimeout
設(shè)置0延時(shí)白魂,并不是同步執(zhí)行,而是交由定時(shí)器線程計(jì)時(shí)并在指定時(shí)間插入到j(luò)s線程的消息隊(duì)列上岗,達(dá)到延遲(異步)執(zhí)行福荸。關(guān)于chrome的最小1ms延遲參見:定時(shí)器(setTimeout/setInterval)最小延遲的問題2.實(shí)際Promise中的
resolve
和reject
有這么神奇的表現(xiàn),其實(shí)就是對(duì)setTimeout
的一層封裝而實(shí)現(xiàn)異步(官方實(shí)現(xiàn)有出入)3.then的鏈?zhǔn)秸{(diào)用肴掷,實(shí)際是每次返回一個(gè)全新的Promise敬锐,其新的Promise對(duì)象在構(gòu)造時(shí)完成對(duì)前一個(gè)Promise的處理。
4.源碼帶上all呆瞻、race這些方法也就恰好199行台夺,不看我廢話其實(shí)挺少的
正文
非官方代碼:
// 判斷變量否為function
const isFunction = variable => typeof variable === 'function'
// 定義Promise的三種狀態(tài)常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
constructor(handle) {
if (!isFunction(handle)) {
throw new Error('MyPromise must accept a function as a parameter')
}
// 添加狀態(tài)
this._status = PENDING
// 添加狀態(tài)
this._value = undefined
// 添加成功回調(diào)函數(shù)隊(duì)列
this._fulfilledQueues = []
// 添加失敗回調(diào)函數(shù)隊(duì)列
this._rejectedQueues = []
// 執(zhí)行handle
try {
handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
// 添加resovle時(shí)執(zhí)行的函數(shù)
_resolve(val) {
const run = () => {
if (this._status !== PENDING) return
// 依次執(zhí)行成功隊(duì)列中的函數(shù),并清空隊(duì)列
const runFulfilled = (value) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
// 依次執(zhí)行失敗隊(duì)列中的函數(shù)痴脾,并清空隊(duì)列
const runRejected = (error) => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
/* 如果resolve的參數(shù)為Promise對(duì)象颤介,則必須等待該P(yáng)romise對(duì)象狀態(tài)改變后,
當(dāng)前Promsie的狀態(tài)才會(huì)改變,且狀態(tài)取決于參數(shù)Promsie對(duì)象的狀態(tài)
*/
if (val instanceof MyPromise) {
val.then(value => {
this._value = value
this._status = FULFILLED
runFulfilled(value)
}, err => {
this._value = err
this._status = REJECTED
runRejected(err)
})
} else {
this._value = val
this._status = FULFILLED
runFulfilled(val)
}
}
// 為了支持同步的Promise赞赖,這里采用異步調(diào)用
setTimeout(run, 0)
}
// 添加reject時(shí)執(zhí)行的函數(shù)
_reject(err) {
if (this._status !== PENDING) return
// 依次執(zhí)行失敗隊(duì)列中的函數(shù)滚朵,并清空隊(duì)列
const run = () => {
this._status = REJECTED
this._value = err
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(err)
}
}
// 為了支持同步的Promise,這里采用異步調(diào)用
setTimeout(run, 0)
}
// 添加then方法
then(onFulfilled, onRejected) {
const { _value, _status } = this
// 返回一個(gè)新的Promise對(duì)象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
// 封裝一個(gè)成功時(shí)執(zhí)行的函數(shù)
let fulfilled = value => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if (res instanceof MyPromise) {
// 如果當(dāng)前回調(diào)函數(shù)返回MyPromise對(duì)象前域,必須等待其狀態(tài)改變后在執(zhí)行下一個(gè)回調(diào)
res.then(onFulfilledNext, onRejectedNext)
} else {
//否則會(huì)將返回結(jié)果直接作為參數(shù)辕近,傳入下一個(gè)then的回調(diào)函數(shù),并立即執(zhí)行下一個(gè)then的回調(diào)函數(shù)
onFulfilledNext(res)
}
}
} catch (err) {
// 如果函數(shù)執(zhí)行出錯(cuò)匿垄,新的Promise對(duì)象的狀態(tài)為失敗
onRejectedNext(err)
}
}
// 封裝一個(gè)失敗時(shí)執(zhí)行的函數(shù)
let rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error)
} else {
let res = onRejected(error);
if (res instanceof MyPromise) {
// 如果當(dāng)前回調(diào)函數(shù)返回MyPromise對(duì)象亏推,必須等待其狀態(tài)改變后在執(zhí)行下一個(gè)回調(diào)
res.then(onFulfilledNext, onRejectedNext)
} else {
//否則會(huì)將返回結(jié)果直接作為參數(shù),傳入下一個(gè)then的回調(diào)函數(shù)年堆,并立即執(zhí)行下一個(gè)then的回調(diào)函數(shù)
onFulfilledNext(res)
}
}
} catch (err) {
// 如果函數(shù)執(zhí)行出錯(cuò)吞杭,新的Promise對(duì)象的狀態(tài)為失敗
onRejectedNext(err)
}
}
switch (_status) {
// 當(dāng)狀態(tài)為pending時(shí),將then方法回調(diào)函數(shù)加入執(zhí)行隊(duì)列等待執(zhí)行
case PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
// 當(dāng)狀態(tài)已經(jīng)改變時(shí)变丧,立即執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
// 添加catch方法
catch(onRejected) {
return this.then(undefined, onRejected)
}
// 添加靜態(tài)resolve方法
static resolve(value) {
// 如果參數(shù)是MyPromise實(shí)例芽狗,直接返回這個(gè)實(shí)例
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
// 添加靜態(tài)reject方法
static reject(value) {
return new MyPromise((resolve, reject) => reject(value))
}
// 添加靜態(tài)all方法
static all(list) {
return new MyPromise((resolve, reject) => {
/**
* 返回值的集合
*/
let values = []
let count = 0
for (let [i, p] of list.entries()) {
// 數(shù)組參數(shù)如果不是MyPromise實(shí)例,先調(diào)用MyPromise.resolve
this.resolve(p).then(res => {
values[i] = res
count++
// 所有狀態(tài)都變成fulfilled時(shí)返回的MyPromise狀態(tài)就變成fulfilled
if (count === list.length) resolve(values)
}, err => {
// 有一個(gè)被rejected時(shí)返回的MyPromise狀態(tài)就變成rejected
reject(err)
})
}
})
}
// 添加靜態(tài)race方法
static race(list) {
return new MyPromise((resolve, reject) => {
for (let p of list) {
// 只要有一個(gè)實(shí)例率先改變狀態(tài)痒蓬,新的MyPromise的狀態(tài)就跟著改變
this.resolve(p).then(res => {
resolve(res)
}, err => {
reject(err)
})
}
})
}
finally(cb) {
return this.then(
value => MyPromise.resolve(cb()).then(() => value),
reason => MyPromise.resolve(cb()).then(() => { throw reason })
);
}
}
看代碼(類名我直接當(dāng)作是Promise了童擎,請(qǐng)注意),按步驟分析主要流程(_resolve
攻晒、then
):
1.Promise構(gòu)造函數(shù)中先初始化4個(gè)成員變量顾复,其中
this._status
代表當(dāng)前狀態(tài)
this._value
用來(lái)存著結(jié)果值
this._fulfilledQueues
和另外一個(gè),用來(lái)存著稍后then方法可能push進(jìn)來(lái)的封裝過(guò)的回調(diào)方法
2.Promise構(gòu)造函數(shù)中馬上調(diào)用傳入的handle函數(shù)鲁捏,給他傳入我們"封裝了setTimeout"的_resolve
方法芯砸。_resolve
即我們new Promise( function(resolve,reject){resolve(10)} )
的那個(gè)resolve。這個(gè)方法只干兩件事:暴露它的參數(shù)10
給內(nèi)部run方法;setTimeout(run,0)來(lái)異步調(diào)用run方法假丧。
而內(nèi)部run
方法主要干三件事情:修改this._status
; 修改this._value
;從this._fulfilledQueues
(或另一個(gè)隊(duì)列)中取出全部函數(shù)并逐一調(diào)用(即cb(value)
)双揪。
小結(jié):當(dāng)value是Promise對(duì)象時(shí),run還會(huì)做一層處理包帚,此類細(xì)節(jié)不再敘述渔期,看源碼比較不啰嗦。此處已能夠幫助我們理解new Promise時(shí)發(fā)生了什么渴邦;為什么函數(shù)會(huì)被執(zhí)行但是resolve處又會(huì)跳過(guò)等等“特性”疯趟,都已經(jīng)非常清晰。
3.then
方法:
上面流程做完谋梭,就等著then方法被調(diào)用了信峻。此時(shí)then方法中會(huì)new一個(gè)Promise返回,而new Promise需要一個(gè)handle函數(shù)當(dāng)參數(shù)章蚣,所以我們只需要關(guān)注這個(gè)then方法中怎么寫這個(gè)“默認(rèn)handle函數(shù)”就行了(注意站欺,new Promise中handle是會(huì)被同步執(zhí)行的,前面提到了)纤垂。
很顯然矾策,“默認(rèn)handle”主要只干一件事情:判斷如何調(diào)用內(nèi)部的fulfilled(value)
函數(shù)。
而fulfilled
主要干兩件事情:調(diào)用then的參數(shù)函數(shù)"onFulfilled(value)"峭沦;調(diào)用新Promise對(duì)象的參數(shù)函數(shù)"onFulfilledNext(value)"(即新Promise對(duì)象中的_resolve
)贾虽。value的處理邏輯看代碼不廢話了。
判斷部分:
1.同步調(diào)用then吼鱼,此時(shí)Promise狀態(tài)為PENDING蓬豁,也就是run
尚未被調(diào)用(只有這個(gè)方法會(huì)改變狀態(tài)的值)。那么我們把fulfilled
送進(jìn)隊(duì)列就行了菇肃,待會(huì)run
被調(diào)用時(shí)地粪,會(huì)清空我們的隊(duì)列并完成調(diào)用。
2.異步調(diào)用then琐谤,如setTimeout(obj.then,100,...省略)
蟆技。此時(shí)我們的run可能被調(diào)用了,從而狀態(tài)為FULFILLED斗忌,意味著this._value
已經(jīng)也被run給修改了质礼,那么直接調(diào)用fulfilled(_value)
(注意this指向問題)
鏈?zhǔn)秸{(diào)用then:
第一個(gè)Promise的then中,最終會(huì)調(diào)用第二個(gè)Promise的_resolve
织阳,從而啟動(dòng)第二個(gè)Promise的setTimeout(run,0)
......剩下就是重復(fù)的邏輯和流程眶蕉,當(dāng)調(diào)用第二個(gè)Promise的then時(shí),把回調(diào)方法同樣封裝后插進(jìn)它的隊(duì)列里面(或異步then導(dǎo)致可能直接執(zhí)行而不進(jìn)隊(duì)列唧躲,就上面的步驟)造挽。
必看
micro-task和macro-task碱璃,即微任務(wù)和宏任務(wù):知乎 Promise的隊(duì)列與setTimeout的隊(duì)列有何關(guān)聯(lián)?
即
setTimeout(function () { console.log(4) }, 0);
new Promise(function (resolve) {
console.log(1)
for (var i = 0; i < 10000; i++) {
i == 9999 && resolve()
}
console.log(2)
}).then(function () { console.log(5) });
console.log(3);
結(jié)果是1,2,3,5,4刽宪。如果改成上面我們的MyPromise厘贼,結(jié)果是1,2,3,4,5界酒。
原因在于V8引擎中圣拄,用的是%EnqueueMicrotask
,是微任務(wù)毁欣;我們這里是直接setTimeout庇谆,是宏任務(wù)。而micro-task和macro-task這兩個(gè)隊(duì)列的執(zhí)行邏輯如下:
這里提及了 macrotask 和 microtask 兩個(gè)概念凭疮,這表示異步任務(wù)的兩種分類饭耳。在掛起任務(wù)時(shí),JS 引擎會(huì)將所有任務(wù)按照類別分到這兩個(gè)隊(duì)列中执解,首先在 macrotask 的隊(duì)列(這個(gè)隊(duì)列也被叫做 task queue)中取出第一個(gè)任務(wù)寞肖,執(zhí)行完畢后取出 microtask 隊(duì)列中的所有任務(wù)順序執(zhí)行;之后再取 macrotask 任務(wù)衰腌,周而復(fù)始新蟆,直至兩個(gè)隊(duì)列的任務(wù)都取完。
macro-task: script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task: process.nextTick, Promises(這里指瀏覽器實(shí)現(xiàn)的原生 Promise), Object.observe, MutationObserver
詳細(xì)點(diǎn)進(jìn)知乎鏈接吧右蕊,不廢話了