Promise實現原理(附原始碼)
本篇文章主要在於探究Promise
的實現原理,帶領大家一步一步實現一個Promise
, 不對其用法做說明,如果讀者還對Promise的用法不瞭解,可以檢視阮一峰老師的ofollow,noindex">ES6 Promise教程
。
接下來,帶你一步一步實現一個Promise
1.Promise
基本結構
new Promise((resolve, reject) => { setTimeout(() => { resolve('FULFILLED') }, 1000) })
建構函式Promise
必須接受一個函式作為引數,我們稱該函式為handle
,handle
又包含resolve
和reject
兩個引數,它們是兩個函式。
定義一個判斷一個變數是否為函式的方法,後面會用到
// 判斷變數否為function const isFunction = variable => typeof variable === 'function'
首先,我們定義一個名為MyPromise
的Class
,它接受一個函式handle
作為引數
class MyPromise { constructor (handle) { if (!isFunction(handle)) { throw new Error('MyPromise must accept a function as a parameter') } } }
再往下看
2.Promise
狀態和值
Promise
物件存在以下三種狀態:
Pending(進行中) Fulfilled(已成功) Rejected(已失敗)
狀態只能由Pending
變為Fulfilled
或由Pending
變為Rejected
,且狀態改變之後不會在發生變化,會一直保持這個狀態。
Promise
的值是指狀態改變時傳遞給回撥函式的值
上文中handle
函式包含resolve
和reject
兩個引數,它們是兩個函式,可以用於改變Promise
的狀態和傳入Promise
的值
new Promise((resolve, reject) => { setTimeout(() => { resolve('FULFILLED') }, 1000) })
這裡resolve
傳入的"FULFILLED"
就是Promise
的值
resolve
和reject
-
resolve
: 將Promise物件的狀態從Pending(進行中)
變為Fulfilled(已成功)
-
reject
: 將Promise物件的狀態從Pending(進行中)
變為Rejected(已失敗)
-
resolve
和reject
都可以傳入任意型別的值作為實參,表示Promise
物件成功(Fulfilled)
和失敗(Rejected)
的值
瞭解了Promise
的狀態和值,接下來,我們為MyPromise
新增狀態屬性和值
首先定義三個常量,用於標記Promise物件的三種狀態
// 定義Promise的三種狀態常量 const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'REJECTED'
再為MyPromise
新增狀態和值,並新增狀態改變的執行邏輯
class MyPromise { constructor (handle) { if (!isFunction(handle)) { throw new Error('MyPromise must accept a function as a parameter') } // 新增狀態 this._status = PENDING // 新增狀態 this._value = undefined // 執行handle try { handle(this._resolve.bind(this), this._reject.bind(this)) } catch (err) { this._reject(err) } } // 新增resovle時執行的函式 _resolve (val) { if (this._status !== PENDING) return this._status = FULFILLED this._value = val } // 新增reject時執行的函式 _reject (err) { if (this._status !== PENDING) return this._status = REJECTED this._value = err } }
這樣就實現了Promise
狀態和值的改變。下面說一說Promise
的核心:then
方法
3.Promise
的then
方法
Promise
物件的then
方法接受兩個引數:
promise.then(onFulfilled, onRejected)
引數可選
onFulfilled
和onRejected
都是可選引數。
-
如果
onFulfilled
或onRejected
不是函式,其必須被忽略
onFulfilled
特性
如果onFulfilled
是函式:
-
當
promise
狀態變為成功時必須被呼叫,其第一個引數為promise
成功狀態傳入的值(resolve
執行時傳入的值) -
在
promise
狀態改變前其不可被呼叫 - 其呼叫次數不可超過一次
onRejected
特性
如果onRejected
是函式:
-
當
promise
狀態變為失敗時必須被呼叫,其第一個引數為promise
失敗狀態傳入的值(reject
執行時傳入的值) -
在
promise
狀態改變前其不可被呼叫 - 其呼叫次數不可超過一次
多次呼叫
then
方法可以被同一個promise
物件呼叫多次
-
當
promise
成功狀態時,所有onFulfilled
需按照其註冊順序依次回撥 -
當
promise
失敗狀態時,所有onRejected
需按照其註冊順序依次回撥
返回
then
方法必須返回一個新的promise
物件
promise2 = promise1.then(onFulfilled, onRejected);
因此promise
支援鏈式呼叫
promise1.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2);
這裡涉及到Promise
的執行規則,包括“值的傳遞”和“錯誤捕獲”機制:
1、如果onFulfilled
或者onRejected
返回一個值x
,則執行下面的Promise
解決過程:[[Resolve]](promise2, x)
-
若
x
不為Promise
,則使x
直接作為新返回的Promise
物件的值, 即新的onFulfilled
或者onRejected
函式的引數. -
若
x
為Promise
,這時後一個回撥函式,就會等待該Promise
物件(即x
)的狀態發生變化,才會被呼叫,並且新的Promise
狀態和x
的狀態相同。
下面的例子用於幫助理解:
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) promise2 = promise1.then(res => { // 返回一個普通值 return '這裡返回一個普通值' }) promise2.then(res => { console.log(res) //1秒後打印出:這裡返回一個普通值 })
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) promise2 = promise1.then(res => { // 返回一個Promise物件 return new Promise((resolve, reject) => { setTimeout(() => { resolve('這裡返回一個Promise') }, 2000) }) }) promise2.then(res => { console.log(res) //3秒後打印出:這裡返回一個Promise })
2、如果onFulfilled
或者onRejected
丟擲一個異常e
,則promise2
必須變為失敗(Rejected)
,並返回失敗的值e
,例如:
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) }) promise2 = promise1.then(res => { throw new Error('這裡丟擲一個異常e') }) promise2.then(res => { console.log(res) }, err => { console.log(err) //1秒後打印出:這裡丟擲一個異常e })
3、如果onFulfilled
不是函式且promise1
狀態為成功(Fulfilled)
,promise2
必須變為成功(Fulfilled)
並返回promise1
成功的值,例如:
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) }) promise2 = promise1.then('這裡的onFulfilled本來是一個函式,但現在不是') promise2.then(res => { console.log(res) // 1秒後打印出:success }, err => { console.log(err) })
4、如果onRejected
不是函式且promise1
狀態為失敗(Rejected)
,promise2
必須變為失敗(Rejected)
並返回promise1
失敗的值,例如:
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { reject('fail') }, 1000) }) promise2 = promise1.then(res => res, '這裡的onRejected本來是一個函式,但現在不是') promise2.then(res => { console.log(res) }, err => { console.log(err) // 1秒後打印出:fail })
根據上面的規則,我們來為 完善MyPromise
修改constructor
: 增加執行佇列
由於then
方法支援多次呼叫,我們可以維護兩個陣列,將每次then
方法註冊時的回撥函式新增到陣列中,等待執行
constructor (handle) { if (!isFunction(handle)) { throw new Error('MyPromise must accept a function as a parameter') } // 新增狀態 this._status = PENDING // 新增狀態 this._value = undefined // 新增成功回撥函式佇列 this._fulfilledQueues = [] // 新增失敗回撥函式佇列 this._rejectedQueues = [] // 執行handle try { handle(this._resolve.bind(this), this._reject.bind(this)) } catch (err) { this._reject(err) } }
新增then方法
首先,then
返回一個新的Promise
物件,並且需要將回調函式加入到執行佇列中
// 新增then方法 then (onFulfilled, onRejected) { const { _value, _status } = this switch (_status) { // 當狀態為pending時,將then方法回撥函式加入執行佇列等待執行 case PENDING: this._fulfilledQueues.push(onFulfilled) this._rejectedQueues.push(onRejected) break // 當狀態已經改變時,立即執行對應的回撥函式 case FULFILLED: onFulfilled(_value) break case REJECTED: onRejected(_value) break } // 返回一個新的Promise物件 return new MyPromise((onFulfilledNext, onRejectedNext) => { }) }
那返回的新的Promise
物件什麼時候改變狀態?改變為哪種狀態呢?
根據上文中then
方法的規則,我們知道返回的新的Promise
物件的狀態依賴於當前then
方法回撥函式執行的情況以及返回值,例如then
的引數是否為一個函式、回撥函式執行是否出錯、返回值是否為Promise
物件。
我們來進一步完善then
方法:
// 新增then方法 then (onFulfilled, onRejected) { const { _value, _status } = this // 返回一個新的Promise物件 return new MyPromise((onFulfilledNext, onRejectedNext) => { // 封裝一個成功時執行的函式 let fulfilled = value => { try { if (!isFunction(onFulfilled)) { onFulfilledNext(value) } else { let res = onFulfilled(value); if (res instanceof MyPromise) { // 如果當前回撥函式返回MyPromise物件,必須等待其狀態改變後在執行下一個回撥 res.then(onFulfilledNext, onRejectedNext) } else { //否則會將返回結果直接作為引數,傳入下一個then的回撥函式,並立即執行下一個then的回撥函式 onFulfilledNext(res) } } } catch (err) { // 如果函式執行出錯,新的Promise物件的狀態為失敗 onRejectedNext(err) } } // 封裝一個失敗時執行的函式 let rejected = error => { try { if (!isFunction(onRejected)) { onRejectedNext(error) } else { let res = onRejected(error); if (res instanceof MyPromise) { // 如果當前回撥函式返回MyPromise物件,必須等待其狀態改變後在執行下一個回撥 res.then(onFulfilledNext, onRejectedNext) } else { //否則會將返回結果直接作為引數,傳入下一個then的回撥函式,並立即執行下一個then的回撥函式 onFulfilledNext(res) } } } catch (err) { // 如果函式執行出錯,新的Promise物件的狀態為失敗 onRejectedNext(err) } } switch (_status) { // 當狀態為pending時,將then方法回撥函式加入執行佇列等待執行 case PENDING: this._fulfilledQueues.push(fulfilled) this._rejectedQueues.push(rejected) break // 當狀態已經改變時,立即執行對應的回撥函式 case FULFILLED: fulfilled(_value) break case REJECTED: rejected(_value) break } }) }
這一部分可能不太好理解,讀者需要結合上文中then
方法的規則來細細的分析。
接著修改_resolve
和_reject
:依次執行佇列中的函式
當resolve
或reject
方法執行時,我們依次提取成功或失敗任務隊列當中的函式開始執行,並清空佇列,從而實現then
方法的多次呼叫,實現的程式碼如下:
// 新增resovle時執行的函式 _resolve (val) { if (this._status !== PENDING) return // 依次執行成功佇列中的函式,並清空佇列 const run = () => { this._status = FULFILLED this._value = val let cb; while (cb = this._fulfilledQueues.shift()) { cb(val) } } // 為了支援同步的Promise,這裡採用非同步呼叫 setTimeout(() => run(), 0) } // 新增reject時執行的函式 _reject (err) { if (this._status !== PENDING) return // 依次執行失敗佇列中的函式,並清空佇列 const run = () => { this._status = REJECTED this._value = err let cb; while (cb = this._rejectedQueues.shift()) { cb(err) } } // 為了支援同步的Promise,這裡採用非同步呼叫 setTimeout(run, 0) }
這裡還有一種特殊的情況,就是當resolve
方法傳入的引數為一個Promise
物件時,則該Promise
物件狀態決定當前Promise
物件的狀態。
const p1 = new Promise(function (resolve, reject) { // ... }); const p2 = new Promise(function (resolve, reject) { // ... resolve(p1); })
上面程式碼中,p1
和p2
都是Promise
的例項,但是p2
的resolve
方法將p1
作為引數,即一個非同步操作的結果是返回另一個非同步操作。
注意,這時p1
的狀態就會傳遞給p2
,也就是說,p1
的狀態決定了p2
的狀態。如果p1
的狀態是Pending
,那麼p2
的回撥函式就會等待p1
的狀態改變;如果p1
的狀態已經是Fulfilled
或者Rejected
,那麼p2
的回撥函式將會立刻執行。
我們來修改_resolve
來支援這樣的特性
// 新增resovle時執行的函式 _resolve (val) { const run = () => { if (this._status !== PENDING) return // 依次執行成功佇列中的函式,並清空佇列 const runFulfilled = (value) => { let cb; while (cb = this._fulfilledQueues.shift()) { cb(value) } } // 依次執行失敗佇列中的函式,並清空佇列 const runRejected = (error) => { let cb; while (cb = this._rejectedQueues.shift()) { cb(error) } } /* 如果resolve的引數為Promise物件,則必須等待該Promise物件狀態改變後, 當前Promsie的狀態才會改變,且狀態取決於引數Promsie物件的狀態 */ 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,這裡採用非同步呼叫 setTimeout(run, 0) }
這樣一個Promise就基本實現了,現在我們來加一些其它的方法
catch
方法
相當於呼叫then
方法, 但只傳入Rejected
狀態的回撥函式
// 新增catch方法 catch (onRejected) { return this.then(undefined, onRejected) }
靜態resolve
方法
// 新增靜態resolve方法 static resolve (value) { // 如果引數是MyPromise例項,直接返回這個例項 if (value instanceof MyPromise) return value return new MyPromise(resolve => resolve(value)) }
靜態reject
方法
// 新增靜態reject方法 static reject (value) { return new MyPromise((resolve ,reject) => reject(value)) }
靜態all
方法
// 新增靜態all方法 static all (list) { return new MyPromise((resolve, reject) => { /** * 返回值的集合 */ let values = [] let count = 0 for (let [i, p] of list.entries()) { // 陣列引數如果不是MyPromise例項,先呼叫MyPromise.resolve this.resolve(p).then(res => { values[i] = res count++ // 所有狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled if (count === list.length) resolve(values) }, err => { // 有一個被rejected時返回的MyPromise狀態就變成rejected reject(err) }) } }) }
靜態race
方法
// 新增靜態race方法 static race (list) { return new MyPromise((resolve, reject) => { for (let p of list) { // 只要有一個例項率先改變狀態,新的MyPromise的狀態就跟著改變 this.resolve(p).then(res => { resolve(res) }, err => { reject(err) }) } }) }
finally
方法
finally
方法用於指定不管Promise
物件最後狀態如何,都會執行的操作
finally (cb) { return this.then( value => MyPromise.resolve(cb()).then(() => value), reason => MyPromise.resolve(cb()).then(() => { throw reason }) ); };
這樣一個完整的Promsie
就實現了,大家對Promise
的原理也有了解,可以讓我們在使用Promise的時候更加清晰明瞭。
完整程式碼如下
// 判斷變數否為function const isFunction = variable => typeof variable === 'function' // 定義Promise的三種狀態常量 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') } // 新增狀態 this._status = PENDING // 新增狀態 this._value = undefined // 新增成功回撥函式佇列 this._fulfilledQueues = [] // 新增失敗回撥函式佇列 this._rejectedQueues = [] // 執行handle try { handle(this._resolve.bind(this), this._reject.bind(this)) } catch (err) { this._reject(err) } } // 新增resovle時執行的函式 _resolve (val) { const run = () => { if (this._status !== PENDING) return // 依次執行成功佇列中的函式,並清空佇列 const runFulfilled = (value) => { let cb; while (cb = this._fulfilledQueues.shift()) { cb(value) } } // 依次執行失敗佇列中的函式,並清空佇列 const runRejected = (error) => { let cb; while (cb = this._rejectedQueues.shift()) { cb(error) } } /* 如果resolve的引數為Promise物件,則必須等待該Promise物件狀態改變後, 當前Promsie的狀態才會改變,且狀態取決於引數Promsie物件的狀態 */ 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,這裡採用非同步呼叫 setTimeout(run, 0) } // 新增reject時執行的函式 _reject (err) { if (this._status !== PENDING) return // 依次執行失敗佇列中的函式,並清空佇列 const run = () => { this._status = REJECTED this._value = err let cb; while (cb = this._rejectedQueues.shift()) { cb(err) } } // 為了支援同步的Promise,這裡採用非同步呼叫 setTimeout(run, 0) } // 新增then方法 then (onFulfilled, onRejected) { const { _value, _status } = this // 返回一個新的Promise物件 return new MyPromise((onFulfilledNext, onRejectedNext) => { // 封裝一個成功時執行的函式 let fulfilled = value => { try { if (!isFunction(onFulfilled)) { onFulfilledNext(value) } else { let res = onFulfilled(value); if (res instanceof MyPromise) { // 如果當前回撥函式返回MyPromise物件,必須等待其狀態改變後在執行下一個回撥 res.then(onFulfilledNext, onRejectedNext) } else { //否則會將返回結果直接作為引數,傳入下一個then的回撥函式,並立即執行下一個then的回撥函式 onFulfilledNext(res) } } } catch (err) { // 如果函式執行出錯,新的Promise物件的狀態為失敗 onRejectedNext(err) } } // 封裝一個失敗時執行的函式 let rejected = error => { try { if (!isFunction(onRejected)) { onRejectedNext(error) } else { let res = onRejected(error); if (res instanceof MyPromise) { // 如果當前回撥函式返回MyPromise物件,必須等待其狀態改變後在執行下一個回撥 res.then(onFulfilledNext, onRejectedNext) } else { //否則會將返回結果直接作為引數,傳入下一個then的回撥函式,並立即執行下一個then的回撥函式 onFulfilledNext(res) } } } catch (err) { // 如果函式執行出錯,新的Promise物件的狀態為失敗 onRejectedNext(err) } } switch (_status) { // 當狀態為pending時,將then方法回撥函式加入執行佇列等待執行 case PENDING: this._fulfilledQueues.push(fulfilled) this._rejectedQueues.push(rejected) break // 當狀態已經改變時,立即執行對應的回撥函式 case FULFILLED: fulfilled(_value) break case REJECTED: rejected(_value) break } }) } // 新增catch方法 catch (onRejected) { return this.then(undefined, onRejected) } // 新增靜態resolve方法 static resolve (value) { // 如果引數是MyPromise例項,直接返回這個例項 if (value instanceof MyPromise) return value return new MyPromise(resolve => resolve(value)) } // 新增靜態reject方法 static reject (value) { return new MyPromise((resolve ,reject) => reject(value)) } // 新增靜態all方法 static all (list) { return new MyPromise((resolve, reject) => { /** * 返回值的集合 */ let values = [] let count = 0 for (let [i, p] of list.entries()) { // 陣列引數如果不是MyPromise例項,先呼叫MyPromise.resolve this.resolve(p).then(res => { values[i] = res count++ // 所有狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled if (count === list.length) resolve(values) }, err => { // 有一個被rejected時返回的MyPromise狀態就變成rejected reject(err) }) } }) } // 新增靜態race方法 static race (list) { return new MyPromise((resolve, reject) => { for (let p of list) { // 只要有一個例項率先改變狀態,新的MyPromise的狀態就跟著改變 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 }) ); } }
如果覺得還行的話,點個贊、收藏一下再走吧。