萬字詳解JavaScript手寫一個(gè)Promise
前言
手寫Promise現(xiàn)在已經(jīng)成了面試的熱門內(nèi)容,但在實(shí)際開發(fā)中基本都不會(huì)去手寫一個(gè)Promise,但是在面試中各種手寫題可能就會(huì)遇到一個(gè)手寫Promise,我們可以盡量提高我們的上限,從而獲取更多的工作機(jī)會(huì)。
Promise核心原理實(shí)現(xiàn)
首先我們從使用的角度來分析一下Promise,然后編寫一個(gè)最簡單版本的Promise。
Promise的使用分析
Promise就是一個(gè)類,在執(zhí)行這個(gè)類的時(shí)候,需要傳遞一個(gè)執(zhí)行器(回調(diào)函數(shù))進(jìn)去,執(zhí)行器會(huì)立即執(zhí)行。
Promise中的狀態(tài)分為三個(gè),分別是:
- pending→等待
- fulfilled→成功
- rejected→失敗
狀態(tài)的切換只有兩種,分別是:
- pending→fulfilled
- pending→rejected
一旦狀態(tài)發(fā)生改變,就不會(huì)再次改變:
- 執(zhí)行器中的兩個(gè)參數(shù),分別是resolve和reject,其實(shí)就是兩個(gè)回調(diào)函數(shù),調(diào)用resolve是從pending狀態(tài)到fulfilled,調(diào)用reject是從狀態(tài)pending到rejected。傳遞給這兩個(gè)回調(diào)函數(shù)的參數(shù)會(huì)作為成功或失敗的值。
- Promise的實(shí)例對(duì)象具有一個(gè)then方法,該方法接受兩個(gè)回調(diào)函數(shù),分別來處理成功與失敗的狀態(tài),then方法內(nèi)部會(huì)進(jìn)行判斷,然后根據(jù)當(dāng)前狀態(tài)確定調(diào)用的回調(diào)函數(shù)。then方法應(yīng)該是被定義在原型對(duì)象中的。
- then的回調(diào)函數(shù)中都包含一個(gè)值,如果是成功,表示成功后返回的值;如果是失敗就表示失敗的原因。
MyPromise的實(shí)現(xiàn)
根據(jù)我們上面的分析,寫出如下代碼:
MyPromise.js
// 定義所有狀態(tài)的常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// Promise實(shí)質(zhì)上就是一個(gè)類,首先創(chuàng)建一個(gè)Promise的類
class MyPromise {
// 實(shí)例化Promise時(shí)需要一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)立即執(zhí)行
constructor(executor) {
// 在調(diào)用executor需要傳遞兩個(gè)回調(diào)函數(shù),分別是resolve和reject
executor(this.resolve, this.reject)
}
// Promise 的狀態(tài)
status = PENDING
// 記錄成功與失敗的值
value = undefined
reason = undefined
resolve = (value) => {// 這里使用箭頭函數(shù)是為了使其內(nèi)部的this指向?yàn)樵搶?shí)例化后的對(duì)象
// 形參value表示,調(diào)用resolve時(shí)傳遞的參數(shù)
// 如果當(dāng)前狀態(tài)不是pending,就直接跳出該邏輯
if (this.status !== PENDING) return
// 將狀態(tài)修改為成功
this.status = FULFILLED
// 將傳入的值進(jìn)行保存
this.value = value
}
reject = (reason) => {// 這里使用箭頭函數(shù)是為了使其內(nèi)部的this指向?yàn)樵搶?shí)例化后的對(duì)象
// 形參reason表示,調(diào)用reject時(shí)傳遞的失敗的原因
// 如果當(dāng)前狀態(tài)不是pending,就直接跳出該邏輯
if (this.status !== PENDING) return
// 將狀態(tài)修改為失敗
this.status = REJECTED
// 保存失敗的原因
this.reason = reason
}
// then方法的實(shí)現(xiàn)
then (onFulfilled, onRejected) {
// 判斷當(dāng)前狀態(tài),根據(jù)狀態(tài)調(diào)用指定回調(diào)
if (this.status === FULFILLED) {
// 將成功的值作為參數(shù)返回
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// 將失敗的原因作為參數(shù)返回
onRejected(this.reason)
}
}
}
// 導(dǎo)出Promise
module.exports = MyPromise 現(xiàn)在我們就來寫一段代碼驗(yàn)證一下上面的代碼
驗(yàn)證resolve:
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
resolve('成功')
})
promise.then(value => {
console.log(value);
}, reason => {
console.log(reason);
})
/* 輸出
成功
*/驗(yàn)證reject:
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
reject('失敗')
})
promise.then(value => {
console.log(value);
}, reason => {
console.log(reason);
})
/* 輸出
失敗
*/驗(yàn)證狀態(tài)不可變:
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
resolve('成功')
reject('失敗')
})
promise.then(value => {
console.log(value);
}, reason => {
console.log(reason);
})
/* 輸出
成功
*/ 在Promise中加入異步操作
如果我們的代碼中存在異步操作,我們自己寫的Promise將毫無用處,
例如下面這段代碼:
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
setTimeout(resolve, 2000, '成功')
})
promise.then(value => {
console.log(value);
}, reason => {
console.log(reason);
})這段代碼2000ms后沒有任何輸出,為了解決這個(gè)問題我們將對(duì)自己編寫的類進(jìn)行如下操作:
- 創(chuàng)建兩個(gè)實(shí)例方法用于存儲(chǔ)我們傳入的成功與失敗的處理邏輯。
- 為
then方法添加狀態(tài)為pending時(shí)的處理邏輯,這時(shí)將傳遞進(jìn)來的屬性保存到實(shí)例上。 - 在成功或者失敗時(shí)調(diào)用相應(yīng)的傳遞進(jìn)來的回調(diào)函數(shù)(實(shí)例屬性存在函數(shù)的情況下)。
實(shí)現(xiàn)代碼如下:
// MyPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
// 存儲(chǔ)成功與失敗的處理邏輯
onFulfilled = undefined
onRejected = undefined
resolve = (value) => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 如果將狀態(tài)修復(fù)為成功,調(diào)用成功的回調(diào)
this.onFulfilled && this.onFulfilled(this.value)
}
reject = (reason) => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 如果將狀態(tài)修復(fù)為失敗,調(diào)用失敗的回調(diào)
this.onRejected && this.onRejected(this.reason)
}
then (onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason)
} else {
// 表示既不是成功,也不是失敗。這個(gè)時(shí)候保存?zhèn)鬟f進(jìn)來的兩個(gè)回調(diào)
this.onFulfilled = onFulfilled
this.onRejected = onRejected
}
}
}
module.exports = MyPromise這里的this.onFulfilled && this.onFulfilled(this.value)表示如果該屬性存在就調(diào)用這個(gè)方法。
現(xiàn)在我們重新執(zhí)行一開始上面那一段代碼,2s后會(huì)成功輸出成功。
實(shí)現(xiàn)then方法的多次調(diào)用
Promise實(shí)例中存在要給then方法,允許我們?cè)赑romise實(shí)例中鏈?zhǔn)秸{(diào)用,每個(gè)then方法還會(huì)返回一個(gè)Promise實(shí)例,
如下圖所示:

Promise實(shí)例方法then是可以多次進(jìn)行調(diào)用,而我們現(xiàn)在自己封裝的卻執(zhí)行調(diào)用一次,現(xiàn)在根據(jù)新需要來重新改寫我們的代碼,
實(shí)現(xiàn)思路如下:
- 定義可以存儲(chǔ)多個(gè)回調(diào)的數(shù)組,用于存儲(chǔ)多個(gè)回調(diào)函數(shù)。
- 如果是同步執(zhí)行的代碼,執(zhí)行后立即知道執(zhí)行結(jié)果,所以可以直接調(diào)用回調(diào)函數(shù)。
- 如果是異步代碼,需要將每次回調(diào)函數(shù)保存到數(shù)組中,然后狀態(tài)變化時(shí)依次調(diào)用函數(shù)。
實(shí)現(xiàn)代碼如下:
// MyPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
// 存儲(chǔ)成功與失敗的處理邏輯
onFulfilled = []
onRejected = []
resolve = (value) => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 如果將狀態(tài)修復(fù)為成功,調(diào)用成功的回調(diào)
while (this.onFulfilled.length) {
// Array.prototype.shift() 用于刪除數(shù)組第一個(gè)元素,并返回
this.onFulfilled.shift()(this.value)
}
}
reject = (reason) => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 如果將狀態(tài)修復(fù)為失敗,調(diào)用失敗的回調(diào)
while (this.onRejected.length) {
// Array.prototype.shift() 用于刪除數(shù)組第一個(gè)元素,并返回
this.onRejected.shift()(this.reason)
}
}
then (onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason)
} else {
// 表示既不是成功,也不是失敗。這個(gè)時(shí)候保存?zhèn)鬟f進(jìn)來的兩個(gè)回調(diào)
this.onFulfilled.push(onFulfilled)
this.onRejected.push(onRejected)
}
}
}
module.exports = MyPromise這里我們通過數(shù)組的shift()方法,該方法刪除數(shù)組的第一個(gè)元素,并返回,返回的這個(gè)值正好是一個(gè)回調(diào)函數(shù),然后調(diào)用該函數(shù)即可實(shí)現(xiàn)該功能。
實(shí)現(xiàn)then的鏈?zhǔn)秸{(diào)用
想要實(shí)現(xiàn)then的鏈?zhǔn)秸{(diào)用,主要解決兩個(gè)問題:
- 返回的是一個(gè)新的
MyPormise實(shí)例。 then的返回值作為下一次的鏈?zhǔn)秸{(diào)用的參數(shù)。
這里分為兩種情況:
- 直接返回一個(gè)值,可以直接作為值使用
- 返回一個(gè)新的
MyPormise實(shí)例,此時(shí)就需要判斷其狀態(tài)
實(shí)現(xiàn)代碼如下:
// MyPromise.js
/* 省略的代碼同上 */
class MyPromise {
/* 省略的代碼同上 */
// then方法的實(shí)現(xiàn)
then (onFulfilled, onRejected) {
// then 方法返回一個(gè)MyPromise實(shí)例
return new MyPromise((resolve, reject) => {
// 判斷當(dāng)前狀態(tài),根據(jù)狀態(tài)調(diào)用指定回調(diào)
if (this.status === FULFILLED) {
// 將成功的值作為參數(shù)返回
// 保存執(zhí)行回調(diào)函數(shù)的結(jié)果
const result = onFulfilled(this.value)
// 如果返回的是一個(gè)普通的值,直接調(diào)用resolve
// 如果是一個(gè)MyPromise實(shí)例,根據(jù)返回的解決來決定是調(diào)用resolve,還是reject
resolvePromise(result, resolve, reject)
} else if (this.status === REJECTED) {
// 將失敗的原因作為參數(shù)返回
onRejected(this.reason)
} else {
// 表示既不是成功,也不是失敗。這個(gè)時(shí)候保存?zhèn)鬟f進(jìn)來的兩個(gè)回調(diào)
this.onFulfilled.push(onFulfilled)
this.onRejected.push(onRejected)
}
})
}
}
function resolvePromise (result, resolve, reject) {
// 判斷傳遞的result是不是MyPromise的實(shí)例對(duì)象
if (result instanceof MyPromise) {
// 說明是MyPromise的實(shí)例對(duì)象
// 調(diào)用.then方法,然后在回調(diào)函數(shù)中獲取到具體的值,然后調(diào)用具體的回調(diào)
// result.then(value => resolve(value), reason => reject(reason))
// 簡寫
result.then(resolve, reject)
} else {
resolve(result)
}
}
module.exports = MyPromisethen方法鏈?zhǔn)秸{(diào)用識(shí)別Promise對(duì)象自返回
在Promise中,如果then方法返回的是自己的promise對(duì)象,則會(huì)發(fā)生promise的嵌套,這個(gè)時(shí)候程序會(huì)報(bào)錯(cuò),
測(cè)試代碼如下:
var promise = new Promise((resolve, reject) => {
resolve(100)
})
var p1 = promise.then(value => {
console.log(value)
return p1
})
// 100
// Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>想要解決這個(gè)問題其實(shí)我們只需要在then方法返回的MyPromise實(shí)例對(duì)象與then中回調(diào)函數(shù)返回的值進(jìn)行比對(duì),如果相同的返回一個(gè)reject的MyPromise實(shí)例對(duì)象,并創(chuàng)建一個(gè)TypeError類型的Error。
實(shí)現(xiàn)代碼如下:
// MyPromise.js
/* 省略的代碼同上 */
then (onFulfilled, onRejected) {
// then 方法返回一個(gè)MyPromise實(shí)例
const promise = new MyPromise((resolve, reject) => {
// 判斷當(dāng)前狀態(tài),根據(jù)狀態(tài)調(diào)用指定回調(diào)
if (this.status === FULFILLED) {
// 這里并不需要延遲執(zhí)行,而是通過setTimeout將其變成異步函數(shù)
// 如果不變成異步的話是在函數(shù)內(nèi)獲取不到promise的
setTimeout(() => {
// 將成功的值作為參數(shù)返回
// 保存執(zhí)行回調(diào)函數(shù)的結(jié)果
const result = onFulfilled(this.value)
// 如果返回的是一個(gè)普通的值,直接調(diào)用resolve
// 如果是一個(gè)MyPromise實(shí)例,根據(jù)返回的解決來決定是調(diào)用resolve,還是reject
resolvePromise(promise, result, resolve, reject)
}, 0)
} else if (this.status === REJECTED) {
// 將失敗的原因作為參數(shù)返回
onRejected(this.reason)
} else {
// 表示既不是成功,也不是失敗。這個(gè)時(shí)候保存?zhèn)鬟f進(jìn)來的兩個(gè)回調(diào)
this.onFulfilled.push(onFulfilled)
this.onRejected.push(onRejected)
}
})
return promise
}
}
function resolvePromise (promise, result, resolve, reject) {
// 這里修改一下該函數(shù),如果return的Promise實(shí)例對(duì)象,也就是傳入的promise===result的話,說明在promise中return的是當(dāng)前promise對(duì)象。
if (promise === result) {
// 這里調(diào)用reject,并拋出一個(gè)Error
// return 是必須的,阻止程序向下執(zhí)行
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 判斷傳遞的result是不是MyPromise的實(shí)例對(duì)象
if (result instanceof MyPromise) {
// 說明是MyPromise的實(shí)例對(duì)象
// 調(diào)用.then方法,然后在回調(diào)函數(shù)中獲取到具體的值,然后調(diào)用具體的回調(diào)
// result.then(value => resolve(value), reason => reject(reason))
// 簡寫
result.then(resolve, reject)
} else {
resolve(result)
}
}
module.exports = MyPromise這里then方法中的setTimeout的作用并不是延遲執(zhí)行,而是為了調(diào)用resolvePromise函數(shù)時(shí),保證創(chuàng)建的promise存在。
捕獲錯(cuò)誤及 then 鏈?zhǔn)秸{(diào)用其他狀態(tài)代碼補(bǔ)充
到目前為止我們現(xiàn)實(shí)的Promise并沒有對(duì)異常做任何處理,為了保證代碼的健壯性,我們需要對(duì)異常做一些處理。
捕獲執(zhí)行器錯(cuò)誤
現(xiàn)在我們需要對(duì)執(zhí)行器進(jìn)行異常捕獲,如果發(fā)生異常,就將我們的狀態(tài)修改為rejected。
捕獲執(zhí)行器的錯(cuò)誤也比較簡單,只需要在構(gòu)造函數(shù)中加入try...catch語句就可以,
實(shí)現(xiàn)代碼如下:
constructor(executor) {
try {
// 在調(diào)用executor需要傳遞兩個(gè)回調(diào)函數(shù),分別是resolve和reject
executor(this.resolve, this.reject)
} catch (e) {
// 發(fā)生異常調(diào)用reject
this.reject(e)
}
}測(cè)試代碼如下:
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
throw new Error('執(zhí)行器錯(cuò)誤')
})
promise.then(value => {
console.log(value);
}, error => {
console.log(error.message);
})
/* 輸出
執(zhí)行器錯(cuò)誤
*/捕獲then中的報(bào)錯(cuò)
現(xiàn)在我們需要對(duì)then中的異常捕獲到,并在下一次鏈?zhǔn)秸{(diào)用中傳遞到then的第二個(gè)函數(shù)中,實(shí)現(xiàn)的方式也是通過try...catch語句,
示例代碼如下:
// then方法的實(shí)現(xiàn)
then (onFulfilled, onRejected) {
// then 方法返回一個(gè)MyPromise實(shí)例
const promise = new MyPromise((resolve, reject) => {
// 判斷當(dāng)前狀態(tài),根據(jù)狀態(tài)調(diào)用指定回調(diào)
if (this.status === FULFILLED) {
// 這里并不需要延遲執(zhí)行,而是通過setTimeout將其變成異步函數(shù)
// 如果不變成異步的話是在函數(shù)內(nèi)獲取不到promise的
setTimeout(() => {
try {
// 將成功的值作為參數(shù)返回
// 保存執(zhí)行回調(diào)函數(shù)的結(jié)果
const result = onFulfilled(this.value)
// 如果返回的是一個(gè)普通的值,直接調(diào)用resolve
// 如果是一個(gè)MyPromise實(shí)例,根據(jù)返回的解決來決定是調(diào)用resolve,還是reject
resolvePromise(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else if (this.status === REJECTED) {
// 將失敗的原因作為參數(shù)返回
onRejected(this.reason)
} else {
// 表示既不是成功,也不是失敗。這個(gè)時(shí)候保存?zhèn)鬟f進(jìn)來的兩個(gè)回調(diào)
this.onFulfilled.push(onFulfilled)
this.onRejected.push(onRejected)
}
})
return promise
}測(cè)試代碼如下:
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
resolve('成功')
})
// 第一個(gè)then方法中的錯(cuò)誤要在第二個(gè)then方法中捕獲到
promise.then(value => {
console.log('resolve', value)
throw new Error('then的執(zhí)行過程中遇到異常')
}).then(null, reason => {
console.log(reason.message)
})
/* 輸出
resolve 成功
then的執(zhí)行過程中遇到異常
*/錯(cuò)誤與異步狀態(tài)的鏈?zhǔn)秸{(diào)用
現(xiàn)在只對(duì)成功狀態(tài)的then進(jìn)行的鏈?zhǔn)秸{(diào)用以及錯(cuò)誤處理,錯(cuò)誤與異步狀態(tài)未進(jìn)行處理,其實(shí)處理起來也是一樣的,
示例代碼如下:
// then方法的實(shí)現(xiàn)
then (onFulfilled, onRejected) {
// then 方法返回一個(gè)MyPromise實(shí)例
const promise = new MyPromise((resolve, reject) => {
// 判斷當(dāng)前狀態(tài),根據(jù)狀態(tài)調(diào)用指定回調(diào)
if (this.status === FULFILLED) {
// 這里并不需要延遲執(zhí)行,而是通過setTimeout將其變成異步函數(shù)
// 如果不變成異步的話是在函數(shù)內(nèi)獲取不到promise的
setTimeout(() => {
try {
// 將成功的值作為參數(shù)返回
// 保存執(zhí)行回調(diào)函數(shù)的結(jié)果
const result = onFulfilled(this.value)
// 如果返回的是一個(gè)普通的值,直接調(diào)用resolve
// 如果是一個(gè)MyPromise實(shí)例,根據(jù)返回的解決來決定是調(diào)用resolve,還是reject
resolvePromise(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else if (this.status === REJECTED) {
// 失敗的處理同成功處理,只是調(diào)用的回調(diào)函數(shù)不同
setTimeout(() => {
try {
const result = onRejected(this.reason)
resolvePromise(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else {
this.onFulfilled.push((value) => {
setTimeout(() => {
try {
const result = onFulfilled(value)
resolvePromise(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejected.push((reason) => {
setTimeout(() => {
try {
const result = onRejected(reason)
resolvePromise(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise
}測(cè)試代碼如下:
const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
setTimeout(resolve, 2000, '成功')
})
// 第一個(gè)then方法中的錯(cuò)誤要在第二個(gè)then方法中捕獲到
promise.then(value => {
console.log('resolve', value)
throw new Error('then的執(zhí)行過程中遇到異常')
}).then(null, reason => {
console.log(reason.message)
})
/* 輸出
resolve 成功
then的執(zhí)行過程中遇到異常
*/將then方法的參數(shù)變成可選參數(shù)
Promise中的then方法其實(shí)是兩個(gè)可以可選參數(shù),如果我們不傳遞任何參數(shù)的話,里面的結(jié)果是向下傳遞的,直到捕獲為止,
例如下面這段代碼:
new Promise((resolve, reject) => {
resolve(100)
})
.then()
.then()
.then()
.then(value => console.log(value))
// 最后一個(gè)then輸入100 這段代碼可以理解為:
new Promise((resolve, reject) => {
resolve(100)
})
.then(value => value)
.then(value => value)
.then(value => value)
.then(value => console.log(value))所以說我們只需要在沒有傳遞回調(diào)函數(shù)時(shí),賦值一個(gè)默認(rèn)的回調(diào)函數(shù)即可。
實(shí)現(xiàn)代碼如下:
// then方法的實(shí)現(xiàn)
then (onFulfilled, onRejected) {
// 如果傳遞函數(shù),就是用傳遞的函數(shù),否則指定一個(gè)默認(rèn)值,用于參數(shù)傳遞
onFulfilled = onFulfilled ? onFulfilled : value => value
// 同理
onRejected = onRejected ? onRejected : reason => { throw reason }
// then 方法返回一個(gè)MyPromise實(shí)例
const promise = new MyPromise((resolve, reject) => {
// 判斷當(dāng)前狀態(tài),根據(jù)狀態(tài)調(diào)用指定回調(diào)
if (this.status === FULFILLED) {...
} else if (this.status === REJECTED) {...
} else {...
}
})
return promise
}Promise.all方法的實(shí)現(xiàn)
關(guān)于all()方法的使用,可以參數(shù)Promise.all()。簡單的說Promise.all()會(huì)將多個(gè)Promise實(shí)例包裝為一個(gè)Promise實(shí)例,且順序與調(diào)用順序一致,
示例代碼如下:
function p1 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1')
}, 2000)
})
}
function p2 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2')
}, 0)
})
}
Promise.all(['a', 'b', p1(), p2(), 'c']).then(result => {
console.log(result)
// ["a", "b", "p1", "p2", "c"]
})在這段代碼中,我們的p1的執(zhí)行是延遲了2s的,這里如果不使用Promise.all()的話最終順序是與我們調(diào)用不同的。
現(xiàn)在我們來分析一下all()的實(shí)現(xiàn)思路:
all()方法可以通過類直接調(diào)用,所以是一個(gè)靜態(tài)方法all()方法接收一個(gè)數(shù)組,數(shù)組中的值可以是一個(gè)普通值,也可以是一個(gè)MyPromise的實(shí)例對(duì)象- return一個(gè)新的MyPromise實(shí)例對(duì)象
- 遍歷數(shù)組中的每一個(gè)值,判斷值得類型,如果是一個(gè)普通值得話直接將值存入一個(gè)數(shù)組;如果是一個(gè)MyPromise的實(shí)例對(duì)象的話,會(huì)調(diào)用then方法,然后根據(jù)執(zhí)行后的狀態(tài),如果失敗的話調(diào)用新的MyPromise實(shí)例對(duì)象中的
rejecte,如果是成功話將這個(gè)值存入一個(gè)數(shù)組 - 存入數(shù)組時(shí)計(jì)數(shù),如果存入的數(shù)量達(dá)到傳入的數(shù)組長度,說明調(diào)用完畢,執(zhí)行
resolve并將最終的結(jié)果數(shù)組作為參數(shù)返回。
實(shí)現(xiàn)代碼:
/**
* @description: 將多個(gè)Promise實(shí)例合并為一個(gè)Promise實(shí)例
* @param {*} array Promise或者普通值
* @returns {Promise}
*/
static all (array) {
// 用于存放最終結(jié)果的數(shù)組
let result = []
// 用于計(jì)算當(dāng)前已經(jīng)執(zhí)行完的實(shí)例的數(shù)量
let count = 0
// 最后返回的是一個(gè)Promise實(shí)例
return new MyPromise((resolve, reject) => {
/**
* @description: 將執(zhí)行完畢的值加入結(jié)果數(shù)組,并根據(jù)實(shí)際情況決定是否調(diào)用resolve
* @param {*} result 存放結(jié)果的數(shù)組
* @param {*} index 要加入的索引
* @param {*} value 要加入數(shù)組的值
* @param {*} resolve Promise中的resolve
*/
function addResult (result, index, value, resolve) {
// 根據(jù)索引值,將結(jié)果堆入數(shù)組中
result[index] = value
// 執(zhí)行完畢一個(gè) count+1,如果當(dāng)前值等于總長度的話說明已經(jīng)執(zhí)行結(jié)束了,可以直接調(diào)用resolve,說明已經(jīng)成功執(zhí)行完畢了
if (++count === array.length) {
// 將執(zhí)行結(jié)果返回
resolve(result)
}
}
// 遍歷穿入的數(shù)組,每個(gè)都執(zhí)行then方法,獲取到最終的結(jié)果
array.forEach((p, index) => {
// 判斷p是不是MyPromise的實(shí)例,如果是的話調(diào)用then方法,不是直接將值加入數(shù)組中
if (p instanceof MyPromise) {
p.then(
// 成功時(shí)將結(jié)果直接加入數(shù)組中
value => {
addResult(result, index, value, resolve)
},
// 如果失敗直接返回失敗原因
reason => {
reject(reason)
}
)
}
else {
addResult(result, index, p, resolve)
}
})
})
}Promise.resolve方法的實(shí)現(xiàn)
關(guān)于Promise.resolve()方法的用法可以參考Promise.resolve()與Promise.reject()。
我們實(shí)現(xiàn)的思路主要如下:
- 該方法是一個(gè)靜態(tài)方法
- 該方法接受的如果是一個(gè)值就將該值包裝為一個(gè)MyPromise的實(shí)例對(duì)象返回,如果是一個(gè)MyPromise的實(shí)例對(duì)象,調(diào)用then方法返回。
實(shí)現(xiàn)代碼如下:
static resolve (value) {
// 如果是MyPromise的實(shí)例,就直接返回這個(gè)實(shí)例
if (value instanceof MyPromise) return value
// 如果不是的話創(chuàng)建一個(gè)MyPromise實(shí)例,并返回傳遞的值
return new MyPromise((resolve) => {
resolve(value)
})
}finally方法的實(shí)現(xiàn)
關(guān)于finally方法可參考finally(),實(shí)現(xiàn)該方法的實(shí)現(xiàn)代碼如下:
finally (callback) {
// 如何拿到當(dāng)前的promise的狀態(tài),使用then方法,而且不管怎樣都返回callback
// 而且then方法就是返回一個(gè)promise對(duì)象,那么我們直接返回then方法調(diào)用之后的結(jié)果即可
// 我們需要在回調(diào)之后拿到成功的回調(diào),所以需要把value也return
// 失敗的回調(diào)也拋出原因
// 如果callback是一個(gè)異步的promise對(duì)象,我們還需要等待其執(zhí)行完畢,所以需要用到靜態(tài)方法resolve
return this.then(value => {
// 把callback調(diào)用之后返回的promise傳遞過去,并且執(zhí)行promise,且在成功之后返回value
return MyPromise.resolve(callback()).then(() => value)
}, reason => {
// 失敗之后調(diào)用的then方法,然后把失敗的原因返回出去。
return MyPromise.resolve(callback()).then(() => { throw reason })
})
}測(cè)試:
function p1 () {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('p1')
}, 2000)
})
}
function p2 () {
return new MyPromise((resolve, reject) => {
reject('p2 reject')
})
}
p2().finally(
() => {
console.log('finally p2')
return p1()
}
).then(
value => {
console.log(value)
}, reason => {
console.log(reason)
}
)
// finally p2
// 兩秒之后執(zhí)行p2 rejectcatch方法的實(shí)現(xiàn)
關(guān)于catch方法可以參考catch(),實(shí)現(xiàn)該方法其實(shí)非常簡單,只需要在內(nèi)部調(diào)用then方法,不傳遞第一個(gè)回調(diào)函數(shù)即可,
實(shí)現(xiàn)代碼如下:
catch (callback) {
return this.then(null, failCallback)
}測(cè)試如下:
function p () {
return new MyPromise((resolve, reject) => {
reject(new Error('reject'))
})
}
p()
.then(value => {
console.log(value)
})
.catch(reason => console.log(reason))完整代碼
// MyPromise.js
// 定義所有狀態(tài)的常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// Promise實(shí)質(zhì)上就是一個(gè)類,首先創(chuàng)建一個(gè)Promise的類
class MyPromise {
// 實(shí)例化Promise時(shí)需要一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)立即執(zhí)行
constructor(executor) {
try {
// 在調(diào)用executor需要傳遞兩個(gè)回調(diào)函數(shù),分別是resolve和reject
executor(this.resolve, this.reject)
} catch (e) {
// 發(fā)生異常調(diào)用reject
this.reject(e)
}
}
// Promise 的狀態(tài)
status = PENDING
// 記錄成功與失敗的值
value = undefined
reason = undefined
// 存儲(chǔ)成功與失敗的處理邏輯
onFulfilled = []
onRejected = []
resolve = (value) => {// 這里使用箭頭函數(shù)是為了使其內(nèi)部的this指向?yàn)樵搶?shí)例化后的對(duì)象
// 形參value表示,調(diào)用resolve時(shí)傳遞的參數(shù)
// 如果當(dāng)前狀態(tài)不是pending,就直接跳出該邏輯
if (this.status !== PENDING) return
// 將狀態(tài)修改為成功
this.status = FULFILLED
// 將傳入的值進(jìn)行保存
this.value = value
// 如果將狀態(tài)修復(fù)為成功,調(diào)用成功的回調(diào)
// this.onFulfilled && this.onFulfilled(this.value)
while (this.onFulfilled.length) {
// Array.prototype.shift() 用于刪除數(shù)組第一個(gè)元素,并返回
this.onFulfilled.shift()(this.value)
}
}
reject = (reason) => {// 這里使用箭頭函數(shù)是為了使其內(nèi)部的this指向?yàn)樵搶?shí)例化后的對(duì)象
// 形參reason表示,調(diào)用reject時(shí)傳遞的失敗的原因
// 如果當(dāng)前狀態(tài)不是pending,就直接跳出該邏輯
if (this.status !== PENDING) return
// 將狀態(tài)修改為失敗
this.status = REJECTED
// 保存失敗的原因
this.reason = reason
// 如果將狀態(tài)修復(fù)為失敗,調(diào)用失敗的回調(diào)
// this.onRejected && this.onRejected(this.reason)
while (this.onRejected.length) {
// Array.prototype.shift() 用于刪除數(shù)組第一個(gè)元素,并返回
this.onRejected.shift()(this.reason)
}
}
// then方法的實(shí)現(xiàn)
then (onFulfilled, onRejected) {
// 如果傳遞函數(shù),就是用傳遞的函數(shù),否則指定一個(gè)默認(rèn)值,用于參數(shù)傳遞
onFulfilled = onFulfilled ? onFulfilled : value => value
// 同理
onRejected = onRejected ? onRejected : reason => { throw reason }
// then 方法返回一個(gè)MyPromise實(shí)例
const promise = new MyPromise((resolve, reject) => {
// 判斷當(dāng)前狀態(tài),根據(jù)狀態(tài)調(diào)用指定回調(diào)
if (this.status === FULFILLED) {
// 這里并不需要延遲執(zhí)行,而是通過setTimeout將其變成異步函數(shù)
// 如果不變成異步的話是在函數(shù)內(nèi)獲取不到promise的
setTimeout(() => {
try {
// 將成功的值作為參數(shù)返回
// 保存執(zhí)行回調(diào)函數(shù)的結(jié)果
const result = onFulfilled(this.value)
// 如果返回的是一個(gè)普通的值,直接調(diào)用resolve
// 如果是一個(gè)MyPromise實(shí)例,根據(jù)返回的解決來決定是調(diào)用resolve,還是reject
resolvePromise(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else if (this.status === REJECTED) {
// 失敗的處理同成功處理,只是調(diào)用的回調(diào)函數(shù)不同
setTimeout(() => {
try {
const result = onRejected(this.reason)
resolvePromise(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
} else {
// 表示既不是成功,也不是失敗。這個(gè)時(shí)候保存?zhèn)鬟f進(jìn)來的兩個(gè)回調(diào)
// this.onFulfilled.push(onFulfilled)
// this.onRejected.push(onRejected)
this.onFulfilled.push((value) => {
setTimeout(() => {
try {
const result = onFulfilled(value)
resolvePromise(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejected.push((reason) => {
setTimeout(() => {
try {
const result = onRejected(reason)
resolvePromise(promise, result, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise
}
catch (callback) {
return this.then(null, callback)
}
finally (callback) {
// 如何拿到當(dāng)前的promise的狀態(tài),使用then方法,而且不管怎樣都返回callback
// 而且then方法就是返回一個(gè)promise對(duì)象,那么我們直接返回then方法調(diào)用之后的結(jié)果即可
// 我們需要在回調(diào)之后拿到成功的回調(diào),所以需要把value也return
// 失敗的回調(diào)也拋出原因
// 如果callback是一個(gè)異步的promise對(duì)象,我們還需要等待其執(zhí)行完畢,所以需要用到靜態(tài)方法resolve
return this.then(value => {
// 把callback調(diào)用之后返回的promise傳遞過去,并且執(zhí)行promise,且在成功之后返回value
return MyPromise.resolve(callback()).then(() => value)
}, reason => {
// 失敗之后調(diào)用的then方法,然后把失敗的原因返回出去。
return MyPromise.resolve(callback()).then(() => { throw reason })
})
}
/**
* @description: 將多個(gè)Promise實(shí)例合并為一個(gè)Promise實(shí)例
* @param {*} array Promise或者普通值
* @returns {Promise}
*/
static all (array) {
// 用于存放最終結(jié)果的數(shù)組
let result = []
// 用于計(jì)算當(dāng)前已經(jīng)執(zhí)行完的實(shí)例的數(shù)量
let count = 0
// 最后返回的是一個(gè)Promise實(shí)例
return new MyPromise((resolve, reject) => {
/**
* @description: 將執(zhí)行完畢的值加入結(jié)果數(shù)組,并根據(jù)實(shí)際情況決定是否調(diào)用resolve
* @param {*} result 存放結(jié)果的數(shù)組
* @param {*} index 要加入的索引
* @param {*} value 要加入數(shù)組的值
* @param {*} resolve Promise中的resolve
*/
function addResult (result, index, value, resolve) {
// 根據(jù)索引值,將結(jié)果堆入數(shù)組中
result[index] = value
// 執(zhí)行完畢一個(gè) count+1,如果當(dāng)前值等于總長度的話說明已經(jīng)執(zhí)行結(jié)束了,可以直接調(diào)用resolve,說明已經(jīng)成功執(zhí)行完畢了
if (++count === array.length) {
// 將執(zhí)行結(jié)果返回
resolve(result)
}
}
// 遍歷穿入的數(shù)組,每個(gè)都執(zhí)行then方法,獲取到最終的結(jié)果
array.forEach((p, index) => {
// 判斷p是不是MyPromise的實(shí)例,如果是的話調(diào)用then方法,不是直接將值加入數(shù)組中
if (p instanceof MyPromise) {
p.then(
// 成功時(shí)將結(jié)果直接加入數(shù)組中
value => {
addResult(result, index, value, resolve)
},
// 如果失敗直接返回失敗原因
reason => {
reject(reason)
}
)
}
else {
addResult(result, index, p, resolve)
}
})
})
}
static resolve (value) {
// 如果是MyPromise的實(shí)例,就直接返回這個(gè)實(shí)例
if (value instanceof MyPromise) return value
// 如果不是的話創(chuàng)建一個(gè)MyPromise實(shí)例,并返回傳遞的值
return new MyPromise((resolve) => {
resolve(value)
})
}
}
function resolvePromise (promise, result, resolve, reject) {
// 這里修改一下該函數(shù),如果return的Promise實(shí)例對(duì)象,也就是傳入的promise===result的話,說明在promise中return的是當(dāng)前promise對(duì)象。
if (promise === result) {
// 這里調(diào)用reject,并拋出一個(gè)Error
// return 是必須的,阻止程序向下執(zhí)行
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 判斷傳遞的result是不是MyPromise的實(shí)例對(duì)象
if (result instanceof MyPromise) {
// 說明是MyPromise的實(shí)例對(duì)象
// 調(diào)用.then方法,然后在回調(diào)函數(shù)中獲取到具體的值,然后調(diào)用具體的回調(diào)
// result.then(value => resolve(value), reason => reject(reason))
// 簡寫
result.then(resolve, reject)
} else {
resolve(result)
}
}
module.exports = MyPromise到此這篇關(guān)于萬字詳解JavaScript手寫一個(gè)Promise的文章就介紹到這了,更多相關(guān)JS手寫Promise內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js輸入框使用正則表達(dá)式校驗(yàn)輸入內(nèi)容的實(shí)例
下面小編就為大家?guī)硪黄猨s輸入框使用正則表達(dá)式校驗(yàn)輸入內(nèi)容的實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
js實(shí)現(xiàn)一個(gè)簡單的MVVM框架示例
下面小編就為大家分享一篇js實(shí)現(xiàn)一個(gè)簡單的MVVM框架示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01
JS實(shí)現(xiàn)拼音(字母)匹配漢字(姓名)的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用JavaScript實(shí)現(xiàn)拼音(字母)匹配(搜索)漢字(姓名)的效果,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2023-04-04
javascript代碼實(shí)現(xiàn)簡易計(jì)算器
這篇文章主要為大家詳細(xì)介紹了javascript代碼實(shí)現(xiàn)簡易計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-01-01
原生JavaScript實(shí)現(xiàn)瀑布流布局
這篇文章主要介紹了原生JavaScript實(shí)現(xiàn)瀑布流布局的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-12-12
JavaScript中Object和Function的關(guān)系小結(jié)
JavaScript 中 Object 和 Function 的關(guān)系是微妙的,他們互為對(duì)方的一個(gè)實(shí)例。2009-09-09

