認真看完這篇文章, 您可以自己封裝一個簡易但功能相對齊全的Promise, 還可以加深對Promise的理解
建議 : 看這篇文章之前希望您
- 瞭解ES6的語法 [ 阮一峰老師的ES6入門 ]
- 瞭解Promises/A+規範 [ Promises/A+ ]
- 會使用Promise
文章較長, 程式碼連貫性較強, 從簡單開始入手, 讀者可以按需選讀
一. 最簡單的Promise
class Promise {
constructor (executor) {
if (typeof executor !== 'function')
throw new TypeError(`Promise resolver ${executor} is not a function`)
/* 預設狀態 */
this.state = 'pending'
this.value = undefined
this.reason = undefined
/*
狀態函式 resolve, reject
1.pending -> fulfilled, pending -> rejected
2.把資料儲存到Promise例項上 this.value = value, this.reason = reason
*/
const resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
}
}
const reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
}
}
executor(resolve, reject)
}
then (onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
if (this.state === 'rejected') {
onRejected(this.reason)
}
}
}
複製程式碼
ps : 測試工具為vsCode的Quokka外掛
根據Promise
的狀態函式res
和rej
,對應執行then
中的處理函式onFulfilled
和onRejected
二. 非同步的Promise
1. then()為非同步
我們都知道,Promise中的then函式的程式碼是非同步執行的,而我們寫的這個並不是,可以驗證一下
顯然這段程式碼是同步執行的,而我們想要的輸出順序是 0 2 1
,所以我們可以使用setTimeout模擬這個非同步
class Promise {
constructor (executor) { ... }
then (onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
/* 使用setTimeout模擬非同步 */
setTimeout(() => {
onFulfilled(this.value)
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
onRejected(this.reason)
}, 0);
}
}
}
複製程式碼
ok, 完美得到我們想要的!
2. 狀態函式非同步執行
當狀態函式res/rej
為非同步執行時, 我們可以看到then
是沒有反應的左邊灰色小方塊表明這行程式碼沒有執行
為什麼呢? 那是因為當執行到then
函式的時候,res
為非同步執行,所以狀態還是pending
,而我們的then
函式裡面還沒有對狀態為pending
的處理, 修改一下程式碼
class Promise {
constructor (executor) {
...
/* 狀態函式非同步執行時, 處理函式的儲存列表 */
this.resolveCallBackList = []
this.rejectCallBackList = []
const resolve = value => {
if (this.state === 'pending') {
...
/* 如果有, 則執行處理函式列表裡的函式 */
this.resolveCallBackList.length > 0
&& this.resolveCallBackList.forEach(e => e())
}
}
const reject = reason => {
if (this.state === 'pending') {
...
this.rejectCallBackList.length > 0
&& this.rejectCallBackList.forEach(e => e())
}
}
...
}
then (onFulfilled, onRejected) {
...
/* 狀態為pending時, 把處理函式儲存對相應的列表 */
if (this.state === 'pending') {
onFulfilled && this.resolveCallBackList.push( () => {
onFulfilled(this.value)
})
onRejected && this.rejectCallBackList.push( () => {
onRejected(this.reason)
})
}
}
}
複製程式碼
這樣, 狀態函式非同步執行的時候也可以處理了, 可以簡單理解為, 當狀態為pending
時, 把處理函式onFulfilled/onRejected
存起來, 等狀態函式res/rej
執行時, 自動執行對應的處理函式
三. Promise的錯誤捕捉
當發生錯誤時, Promise
不會報錯, 而是由失敗的處理函式then函式的第二個函式
捕捉錯誤並處理, 如果我們自己寫的Promise
發生錯誤的話, 毫無意外是直接報錯的, 就像這樣
既然執行時發生錯誤, 那麼我們就可以使用try/catch
去捕獲錯誤
class Promise {
constructor (executor) {
...
/* 使用try/catch捕獲錯誤, 並執行reject, 改變狀態為rejected */
try {
executor(resolve, reject)
} catch (error) {
this.state === 'pending' && reject(error)
}
}
then (onFulfilled, onRejected) { ... }
}
複製程式碼
四. then函式詳解
then函式有兩個特性
- then函式執行完返回一個新的Promise例項
- then函式能鏈式呼叫
1. then的鏈式呼叫
new Promise(res => res(0))
.then(value => {
console.log(value) // 0
return `1 fulfilled`
})
.then(value => {
console.log(value) // 1 fulfilled
})
複製程式碼
then
函式執行後返回一個Promise
例項, 該Promise
例項的狀態由then
決定, 下一個then
函式根據返回的這個Promise
例項執行相應的處理函式, 畫個圖
then
的執行依賴於上一個then
執行返回的Promise
例項, 而這個Promise
例項的資料由上一個then
的處理函式onFulfilled/onRejected
的執行和其返回值
決定
2.then的處理函式返回值不是一個Promise例項
如果按照字面意思去寫程式碼
class Promise {
constructor (executor) { ... }
then (onFulfilled, onRejected) {
/* 一個新的Promise例項 */
const newPromise = new Promise ( (res, rej) => {})
...
return newPromise
}
}
複製程式碼
如果這樣寫, 是沒意義的, 返回的Promise
例項的狀態永遠為pending
, 因為沒有執行狀態函式res/rej
, 因此也無法進行then
函式的鏈式呼叫
因為new Promise(executor)
的executor
函式是同步執行的, 所以我們可以這樣寫
class Promise {
constructor (executor) { ... }
then (onFulfilled, onRejected) {
const newPromise = new Promise ( (res, rej) => {
/*
這部分的處理函式是同步執行的, 因此可以放在裡面執行
同時還能通過res/rej改變返回的Promise例項的狀態
*/
if (this.state === 'fulfilled') {
setTimeout(() => {
/* 拿到處理函式執行後的返回值 */
const value = onFulfilled(this.value)
/* 改變返回的Promise例項的狀態並把資料傳過去 */
res(value)
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
const reason = onRejected(this.reason)
res(reason)
}, 0);
}
if (this.state === 'pending') {
onFulfilled && this.resolveCallBackList.push( () => {
const value = onFulfilled(this.value)
res(value)
})
onRejected && this.rejectCallBackList.push( () => {
const reason = onRejected(this.reason)
res(reason)
})
}
})
return newPromise
}
}
複製程式碼
噠噠, then
的鏈式呼叫完成了
ps : then
的處理函式返回值不是一個Promise
例項時, 無論fullfilled
還是rejected
, 都是執行下一個then
函式的onFulfilled
3.then的處理函式返回值是一個Promise例項
當then
的處理函式返回值是一個Promise
例項時, 則下一個then
函式的執行, 全部由這個Promise
例項決定, 所以我們需要使用checkReturnValueIfPromise
函式去判斷一下返回值的型別並處理對應的情況
class Promise {
constructor (executor) { ... }
/*
promise -> Promise物件
target -> then的處理函式的返回值
res/rej -> 要返回的Promise例項的狀態函式
*/
checkReturnValueIfPromise (promise, target, res, rej) {
if (target instanceof promise) {
/*
如果是Promise例項
則呼叫then函式,根據Promise例項的狀態執行對應的處理函式
從而改變要返回的Promise例項的狀態
如果下面的程式碼不能理解, 也可以寫成這樣
target.then( value => {
res(value)
}, reason => {
rej(reason)
} )
*/
target.then(res, rej)
} else {
res(target)
}
}
then (onFulfilled, onRejected) {
const newPromise = new Promise ( (res, rej) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
const value = onFulfilled(this.value)
/* 呼叫檢測函式並做相關處理 */
this.checkReturnValueIfPromise(Promise, value, res, rej)
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
const reason = onRejected(this.reason)
this.checkReturnValueIfPromise(Promise, reason, res, rej)
}, 0);
}
if (this.state === 'pending') {
onFulfilled && this.resolveCallBackList.push( () => {
const value = onFulfilled(this.value)
this.checkReturnValueIfPromise(Promise, value, res, rej)
})
onRejected && this.rejectCallBackList.push( () => {
const reason = onRejected(this.reason)
this.checkReturnValueIfPromise(Promise, reason, res, rej)
})
}
})
return newPromise
}
}
複製程式碼
就算是非同步也是一點毛病都沒有
五. 一些Promise上的方法 (直接上程式碼)
對了, 還有一個與then
類似的方法catch
, 這個方法是專門處理rejected
狀態的, 程式碼也就只有一句話
class Promise {
constructor () { ... }
then () { ... }
catch (onRejected) {
this.then(undefined, onRejected)
}
}
複製程式碼
1. Promise.resolve
返回一個fulfilled
狀態的Promise
例項
class Promise {
constructor () { ... }
then () { ... }
catch () { ... }
static resolve (value) {
return new Promise( res => res(value))
}
}
複製程式碼
2. Promise.reject
返回一個rejected
狀態的Promise
例項
class Promise {
constructor () { ... }
then () { ... }
catch () { ... }
static resolve () { ... }
static reject (reason) {
return new Promise( (undefined, rej) => rej(reason))
}
}
複製程式碼
3. Promise.race
接收一個Promise
例項的陣列promiseArray
, 返回一個Promise
例項, 返回的Promise
例項由promiseArray
中執行最快的Promise
例項決定
class Promise {
constructor () { ... }
then () { ... }
catch () { ... }
static resolve () { ... }
static reject () { ... }
static race (promiseArray) {
return new Promise ( (res, rej) => {
promiseArray.forEach( promise => {
promise.then(res, rej)
})
})
}
}
複製程式碼
4. Promise.all
功能描述太長了, 不懂的可以去看 阮一峰老師對於Promise.all的介紹
class Promise {
constructor () { ... }
then () { ... }
catch () { ... }
static resolve () { ... }
static reject () { ... }
static race () { ... }
static all (promiseArray) {
let count = 0,
resultArray = []
return new Promise( (res, rej) => {
promiseArray.forEach( promise => {
promise.then( value => {
count++
resultArray.push(value)
if (count === promiseArray.length) {
res(resultArray)
}
}, reason => {
rej(reason)
})
})
})
}
}
複製程式碼
六. 完整的程式碼 (帶註解)
上面的程式碼也不是完美的, 還有一些細枝末節的問題沒解決, 不過也完成了核心的功能, 下面給出稍微完整的程式碼(帶註解)
class Promise {
constructor (executor) {
if (typeof executor !== 'function') {
throw new TypeError(`Promise resolver ${executor} must be a function`)
}
this.state = 'pending'
this.value = undefined
this.reason = undefined
/* 非同步時, then()函式的執行函式(onFulfilled, onRejected)的儲存列表 */
this.resolveCallBackList = []
this.rejectCallBackList = []
/**
* @method resolve
* @param {string} value 成功時的引數
* @function 改變狀態, 傳遞引數, 遍歷成功非同步快取函式列表
* @returns {undefined}
*/
const resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.resolveCallBackList.length > 0 && this.resolveCallBackList.forEach(e => e())
}
}
/**
* @method reject
* @param {string} reason 失敗時的引數
* @function 改變狀態, 傳遞引數, 遍歷失敗非同步快取函式列表
* @returns {undefined}
*/
const reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.rejectCallBackList.length > 0 && this.rejectCallBackList.forEach(e => e())
}
}
/* 例項 Promise過程發生錯誤, 則直接把狀態改為 rejected */
try {
executor(resolve, reject)
} catch (error) {
this.state === 'pending' && reject(error)
}
}
/**
* @method checkLastThenReturn
* @param {Promise} promise Promise物件
* @param {*} target then處理函式(onFulfilled/onRejected)的返回值
* @param {function} res 下一個Promise例項的成功狀態轉換函式
* @param {function} rej 下一個Promise例項的失敗狀態轉換函式
* @function 判斷then()的處理函式的返回值是否為新的Promise例項
*/
checkLastThenReturn (promise, target, res, rej) {
if (target instanceof promise) {
/* 如果target是一個新的 Promise例項,則根據該例項的執行,改變下一個Promise例項的狀態 */
target.then(res, rej)
} else {
/* 如果不是, 則直接執行成功的狀態改變函式 */
res(target)
}
}
/**
* @method then
* @param {function} onFulfilled 狀態為fulfilled時執行
* @param {function} onRejected 狀態為rejected時執行
* @function 返回一個 Promise例項, 該Promise例項的狀態,由該 then()的執行情況決定
* @returns {Promise} 返回一個新的Promise物件
*/
then (onFulfilled, onRejected) {
/* 處理 then()不傳引數且上一個操作為非同步時, 直接返回上一個例項 */
if (!onFulfilled && !onRejected && this.state === 'pending') return this
/* 處理 then(onFulfilled)只有成功處理函式但狀態為 rejected的情況, 直接返回一個失敗的Promise例項 */
if (!onRejected && this.state === 'rejected') return Promise.reject(this.reason)
/* 處理 then()不傳參的問題,直接把處理函式重置為返回接收的引數 */
if (!onFulfilled && !onRejected) {
onFulfilled = value => value
onRejected = reason => reason
}
/**
* @method returnPromise
* @param {function} res 使返回的Promise物件狀態轉為 fulfilled
* @param {function} rej 使返回的Promise物件狀態轉為 rejected
* @function 同步執行,根據then()的執行情況,執行res還是 rej,改變該Promise例項的狀態並執行非同步儲存函式列表
*/
const returnPromise = new Promise( (res, rej) => {
/* then()的處理函式同步執行 */
if (this.state === 'fulfilled') {
/* 使用setTimeout模擬 then()裡面的內容非同步執行 */
setTimeout(() => {
/* 如果處理函式出錯,則返回的 Promise例項為 rejected */
try {
const value = onFulfilled(this.value)
this.checkLastThenReturn(Promise, value, res, rej)
} catch (error) {
rej(error)
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const reason = onRejected(this.reason)
this.checkLastThenReturn(Promise, reason, res, rej)
} catch (error) {
rej(error)
}
}, 0);
}
/* then()處理函式非同步執行 */
if (this.state === 'pending') {
onFulfilled && this.resolveCallBackList.push( () => {
try {
const value = onFulfilled(this.value)
this.checkLastThenReturn(Promise, value, res, rej)
} catch (error) {
rej(error)
}
})
if (onRejected) {
this.rejectCallBackList.push( () => {
try {
const reason = onRejected(this.reason)
this.checkLastThenReturn(Promise, reason, res, rej)
} catch (error) {
rej(error)
}
})
} else {
/* 非同步時沒有onRejected, 直接把下一個Promise物件變為失敗狀態 */
this.rejectCallBackList.push( () => {
// Promise.reject(this.reason).catch(rej)
rej(this.reason)
})
}
}
})
return returnPromise
}
/**
* @method catch
* @param {function} onRejected 失敗時的處理函式
* @function 沒有onFulfilled的 then()
* @returns {Promise}
*/
catch (onRejected) {
this.then(undefined, onRejected)
}
/**
* @method reject
* @param {string} reason
* @function 返回一個失敗狀態的Promise例項
* @returns {new Promise}
*/
static reject (reason) {
return new Promise( (undefined, rej) => rej(reason))
}
/**
* @method resolve
* @param {string} value
* @function 返回一個成功狀態的Promise例項
* @returns {new Promise}
*/
static resolve (value) {
return new Promise( res => res(value))
}
/**
* @method race
* @param {array} promiseArray Promise例項的陣列
* @function 找出Promise例項陣列中執行最快的 Promise例項
* @returns 返回一個Promise例項,該例項的狀態由 Promise例項陣列中執行最快的一個 Promise例項決定
*/
static race (promiseArray) {
return new Promise ( (res, rej) => {
promiseArray.forEach( promise => {
promise.then(res, rej)
})
})
}
/**
* @method all
* @param {array} promiseArray Promise例項的陣列
* @function 等Promise例項陣列的每個 Promise例項都執行完再把所有執行的結果放進一個陣列返回,如果出錯,則終止並返回出錯那個Promise的資料
* @returns 返回一個 Promise例項
*/
static all (promiseArray) {
let count = 0,
resultArray = []
return new Promise( (res, rej) => {
promiseArray.forEach( promise => {
promise.then( value => {
count++
resultArray.push(value)
if (count === promiseArray.length) {
res(resultArray)
}
}, reason => {
rej(reason)
})
})
})
}
}
複製程式碼
七. 結語
謝謝瀏覽我的文章, 希望你能學到東西