本篇文章主要在於探究
Promise
的實現原理,帶領大家一步一步實現一個Promise
, 不對其用法做說明,如果讀者還對Promise的用法不瞭解,可以檢視阮一峰老師的ES6 Promise教程。
接下來,帶你一步一步實現一個 Promise
1. Promise
基本結構
1 2 3 4 5 6 |
new Promise((resolve, reject) => { setTimeout(() => { resolve('FULFILLED') }, 1000) }) |
建構函式
Promise
必須接受一個函式作為引數,我們稱該函式為handle
,handle
又包含resolve
和reject
兩個引數,它們是兩個函式。
定義一個判斷一個變數是否為函式的方法,後面會用到
1 2 3 |
// 判斷變數否為function const isFunction = variable => typeof variable === 'function' |
首先,我們定義一個名為 MyPromise
的 Class
,它接受一個函式 handle
作為引數
1 2 3 4 5 6 7 8 |
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
的值
1 2 3 4 5 6 |
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物件的三種狀態
1 2 3 4 5 |
// 定義Promise的三種狀態常量 const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'REJECTED' |
再為
MyPromise
新增狀態和值,並新增狀態改變的執行邏輯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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
方法接受兩個引數:
1 2 |
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
物件
1 2 |
promise2 = promise1.then(onFulfilled, onRejected); |
因此 promise
支援鏈式呼叫
1 2 |
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
的狀態相同。
下面的例子用於幫助理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) promise2 = promise1.then(res => { // 返回一個普通值 return '這裡返回一個普通值' }) promise2.then(res => { console.log(res) //1秒後列印出:這裡返回一個普通值 }) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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
成功的值,例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
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
失敗的值,例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
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
方法註冊時的回撥函式新增到陣列中,等待執行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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
物件,並且需要將回撥函式加入到執行佇列中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 新增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
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
// 新增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
方法的多次呼叫,實現的程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// 新增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
物件的狀態。
1 2 3 4 5 6 7 8 9 |
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
來支援這樣的特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// 新增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
狀態的回撥函式
1 2 3 4 5 |
// 新增catch方法 catch (onRejected) { return this.then(undefined, onRejected) } |
靜態 resolve
方法
1 2 3 4 5 6 7 |
// 新增靜態resolve方法 static resolve (value) { // 如果引數是MyPromise例項,直接返回這個例項 if (value instanceof MyPromise) return value return new MyPromise(resolve => resolve(value)) } |
靜態 reject
方法
1 2 3 4 5 |
// 新增靜態reject方法 static reject (value) { return new MyPromise((resolve ,reject) => reject(value)) } |
靜態 all
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// 新增靜態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
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 新增靜態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
物件最後狀態如何,都會執行的操作
1 2 3 4 5 6 7 |
finally (cb) { return this.then( value => MyPromise.resolve(cb()).then(() => value), reason => MyPromise.resolve(cb()).then(() => { throw reason }) ); }; |
這樣一個完整的 Promsie
就實現了,大家對 Promise
的原理也有了解,可以讓我們在使用Promise的時候更加清晰明瞭。
完整程式碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
// 判斷變數否為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 }) ); } } |
如果覺得還行的話,點個贊、收藏一下再走吧。