由淺入深,從掌握Promise的基本使用到手寫Promise
前言
在ES6之前,對於一些非同步任務的處理始終沒有很好的方案可以解決,處理非同步的方案可謂是十分混亂,在業務需求下非同步請求的套用,就形成了回撥地獄,嚴重影響程式碼的閱讀性。而Promise的出現,給我們統一了規範,解決了之前處理非同步任務的許多痛點,並且它友好的使用方式,使之成為了JavaScript一大重點,同時也是面試的高頻問點,下面就一起來全面認識一下Promise吧。
1.什麼是Promise?
如果我們想在一個非同步請求之後,拿到請求的結果,在ES6之前我們可以怎麼做呢?
比如,給定一個請求地址,希望拿到它請求成功或者失敗的結果:
- 可以通過分別設定成功和失敗的兩個回撥;
- 當請求成功後呼叫成功的回撥,將成功的結果傳遞過去;
- 當請求失敗後呼叫失敗的回撥,將失敗的結果傳遞過去;
function request(url, successCb, failCb) {
setTimeout(function() {
if (url === '/aaa/bbb') { // 請求成功
let res = [1, 2, 3]
successCb(res)
} else { // 請求失敗
let err = 'err message'
failCb(err)
}
})
}
// 呼叫方式,從回撥中拿結果
request('/aaa/bbb', function(res) {
console.log(res)
}, function(err) {
console.log(err)
})
將上面的情況使用Promise來實現一下:
- Promise是一個類,通過new呼叫,可以給予呼叫者一個承諾;
- 通過new建立Promise物件時,需要傳入一個回撥函式,這個回撥函式稱之為executor,executor接收兩個引數resolve和reject;
- 傳入的回撥會被立即執行,當呼叫resolve函式時,會去執行Promise物件的then方法中傳入的成功回撥;
- 當呼叫reject函式時,會去執行Promise物件的then方法中傳入的失敗回撥函式,並且請求後的結果可以通過引數傳遞過去;
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url === '/aaa/bbb') {
let res = [1, 2, 3]
resolve(res) // 請求成功呼叫resolve
} else {
let err = 'err message'
reject(err) // 請求失敗呼叫reject
}
})
})
}
const p = request('/aaa/bbb')
p.then(res => {
console.log(res) // 拿到resolve傳遞過來的值
}, err => {
console.log(err) // 拿到reject傳遞過來的值
})
2.Promise的三種狀態
為什麼Promise能夠將請求的結果準確的傳遞到then中的回撥函式中,因為Promise其核心就用三種狀態來進行管控。
- 待定狀態(pending):Promise的初始狀態;
- 已兌現(resolved、fulfilled):操作成功,如執行resolve時就變為該狀態;
- 已拒絕(rejected):操作失敗,如執行reject時就變為該狀態;
通過上面的案例,可以在瀏覽器中檢視Promise分別在執行resolve和reject後的列印結果和Promise當時處於的狀態:
-
resolve和reject都沒執行:
-
執行resolve,請求成功:
-
執行reject,請求失敗:
注意:在後續的對Promise的講述過程中,都需要帶著Promise的狀態去理解。
3.executor
executor是在建立Promise是需要傳入的一個回撥函式,這個回撥函式會被立即執行,並且傳入兩個引數,分別就是resolve和reject。
new Promise((resolve, reject) => {
console.log('我是executor中的程式碼,我會被立即執行~')
})
通常我們會在executor中確定Promise的狀態,而且狀態一旦被確定下來,Promise的狀態就會被鎖死,即Promise的狀態一旦修改,就不能再次更改了。
- 當呼叫resolve,如果resolve傳入的值不是一個Promise(即傳入的值為一個普通值),Promise的狀態就會立即變成fulfilled;
- 但是,如果在resolve後接著呼叫reject,是不會有任何的效果的,因為reject已經無法改變Promise的結果了;
4.resolve的引數
上面聊到了resolve需要傳入一個普通值,Promise的狀態才會被立即鎖定為fulfilled,那麼如果傳遞的不是普通值呢?一般resolve傳遞以下三類值,會有不同的表現效果。
-
傳值一:resolve傳入一個普通值或普通物件,那麼這個值會作為then中第一個回撥的引數;
const p = new Promise((resolve, reject) => { resolve(123) }) p.then(res => { console.log(res) // 123 })
-
傳值二:resolve傳入一個Promise,那麼這個傳入的Promise會決定原來Promise的狀態;
-
傳入的Promise呼叫的是resolve;
const newP = new Promise((resolve, reject) => { resolve(123) }) const p = new Promise((resolve, reject) => { resolve(newP) }) p.then(res => { console.log(res) // 123 }, err => { console.log(err) })
-
傳入的Promise呼叫的是reject;
const newP = new Promise((resolve, reject) => { reject('err message') }) const p = new Promise((resolve, reject) => { resolve(newP) }) p.then(res => { console.log(res) }, err => { console.log(err) // err message })
-
-
傳值三:resolve傳入一個特殊物件,該物件中實現了then方法,那麼Promise的狀態就是物件中then方法執行後的結果來決定的;
-
then中執行了resolve;
const obj = { then: function(resolve, reject) { resolve(123) } } const p = new Promise((resolve, reject) => { resolve(obj) }) p.then(res => { console.log(res) // 123 }, err => { console.log(err) })
-
then中執行了reject;
const obj = { then: function(resolve, reject) { reject('err message') } } const p = new Promise((resolve, reject) => { resolve(obj) }) p.then(res => { console.log(res) }, err => { console.log(err) // err message })
-
5.Promise相關例項方法
Promise的例項方法,就是可以通過其例項物件進行呼叫的方法。
5.1.then方法
then方法是Promise例項物件上的一個方法:
Promise.prototype.then
(1)then方法接收兩個引數
- 狀態變成fulfilled的回撥函式;
- 狀態變成rejected的回撥函式;
promise.then(res => {
console.log('狀態變成fulfilled回撥')
}, err => {
console.log('狀態變成rejected回撥')
})
(2)then方法多次呼叫
- 一個Promise的then方法是可以被多次呼叫的,每次呼叫都可以傳入對應的fulfilled回撥;
- 當Promise的狀態變成fulfilled的時候,這些回撥函式都會被執行;
- 反之,當Promise的狀態變成rejected,所有then中傳入的rejected回撥都會被執行;
const p = new Promise((resolve, reject) => {
resolve('aaa')
})
p.then(res => {
console.log(res) // aaa
})
p.then(res => {
console.log(res) // aaa
})
p.then(res => {
console.log(res) // aaa
})
(3)then方法中的返回值
then呼叫本身是有返回值的,並且它的返回值是一個Promise,所以then可以進行鏈式呼叫,但是then方法呼叫的返回值的狀態是什麼呢?主要是由其返回值決定的。
-
當then方法中的回撥在執行時處於pending狀態;
-
當then方法中的回撥返回一個結果時處於fulfilled狀態,並且會將結果作為resolve的引數;
-
返回一個普通的值:這個普通的值會被作為一個新Promise的resolve中的值
p.then(res => { return 123 // 相當於: /* return new Promise((resolve, reject) => { resolve(123) }) */ }).then(res => { console.log(res) // 123 })
-
返回一個實現了then方法的物件:
p.then(res => { const obj = { then: function(resolve, reject) { resolve('abc') } } return obj // 相當於: /* return new Promise((resolve, reject) => { resolve(obj.then) }) */ }).then(res => { console.log(res) // abc })
-
返回一個Promise:
p.then(res => { const newP = new Promise((resolve, reject) => { resolve(123) }) return newP // 相當於: /* const newP = new Promise((resolve, reject) => { resolve(123) }) return new Promise((resolve, reject) => { resolve(newP) }) */ }).then(res => { console.log(res) // 123 })
-
-
當then方法執行時丟擲一個異常,就處於rejected狀態,同樣,Promise的executor在執行的時候丟擲異常,Promise對應的狀態也會變成rejected;
const p = new Promise((resolve, reject) => { throw new Error('err message') }) p.then(res => { console.log(res) }, err => { console.log(err) // Error: err message return new Error('then err message') }).then(res => { console.log(res) }, err => { console.log(err) // Error: then err message })
5.2.catch方法
catch方法是Promise例項物件上的一個方法:
Promise.prototype.catch
(1)catch方法可多次呼叫
- 一個Promise的catch方法也是可以被多次呼叫的,每次呼叫都可以傳入對應的reject回撥;
- 當Promise的狀態變成rejected的時候,這些回撥就都會執行;
- catch方法的效果和then方法的第二個回撥函式差不多,可用於替代then方法的第二個回撥;
const p = new Promise((resolve, reject) => {
reject('err message')
})
p.catch(err => {
console.log(err) // err message
})
p.catch(err => {
console.log(err) // err message
})
p.catch(err => {
console.log(err) // err message
})
(2)catch方法的返回值
-
catch方法的執行返回的也是一個Promise物件,使用catch後面可以繼續呼叫then方法或者catch方法;
-
如果在catch後面呼叫then方法,會進入到then方法的fulfilled回撥函式中,因為catch返回的Promise預設是fulfilled;
p.catch(err => { return 'catch return value' }).then(res => { console.log(res) // catch return value })
-
如果catch後續又呼叫了catch,那麼可以丟擲一個異常,就會進入後面的catch回撥中;
p.catch(err => { throw new Error('catch err message') }).catch(err => { console.log(err) // Error: catch err message })
(3)catch的作用
-
catch主要是用於捕獲異常的,當executor丟擲異常是,可以通過catch進行處理;
-
注意:當Promise的executor執行reject或者丟擲異常,後續必須要有捕獲異常的處理,如下程式碼,雖然都呼叫了then方法,接著後續又呼叫了catch方法,但是then和catch是兩次獨立的呼叫,兩次呼叫並沒有聯絡,所以就被認定為沒有處理異常。
const p = new Promise((resolve, reject) => { reject('err message') }) p.then(res => { console.log(res) }) p.catch(err => { console.log(err) })
-
正確處理的方法為:
// 方法一: p.then(res => { console.log(res) }).catch(err => { console.log(err) }) // 方法二: p.then(res => { console.log(res) }, err => { console.log(err) })
5.3.finally方法
finally方法是Promise例項物件上的一個方法:
Promise.prototype.finally
- finally是在ES9中新增的,無論Promise的狀態變成fulfilled還是rejected,最終都會執行finally中的回撥;
- 注意finally是不接收引數的,因為它必定執行;
const p = new Promise((resolve, reject) => {
resolve(123)
})
p.then(res => {
console.log(res) // 123
}).catch(err => {
console.log(err)
}).finally(() => {
console.log('finally code') // finally code
})
6.Promise相關類方法
Promise的類方法,就是直接通過Promise進行呼叫。
6.1.resolve方法
resolve方法具體有什麼用呢?當我們希望將一個值轉成Promise來使用,就可以通過直接呼叫resolve方法來實現,其效果就相當於在new一個Promise時在executor中執行了resolve方法。
resolve傳入的引數型別:
-
引數為一個普通的值;
const p = Promise.resolve('aaaa') // 相當於: /* const p = new Promise((resolve, reject) => { resolve('aaaa') }) */ console.log(p)
-
引數為一個實現了then方法的物件;
const p = Promise.resolve({ then: function(resolve, reject) { resolve('aaaa') } }) // 相當於: /* const p = new Promise((resolve, reject) => { resolve({ then: function(resolve, reject) { resolve('aaaa') } }) }) */ console.log(p)
-
引數為一個Promise;
const p = Promise.resolve(new Promise((resolve, reject) => { resolve('abc') })) // 相當於: /* const p = new Promise((resolve, reject) => { resolve(new Promise((resolve, reject) => { resolve('abc') })) }) */ console.log(p)
6.2.reject方法
reject方法和resolve的用法一致,只不過是將可以得到一個狀態為rejected的Promise物件,並且reject不過傳入的是什麼引數,都會原封不動作為rejected狀態傳遞到catch中。
// 1.傳入普通值
const p1 = Promise.reject(123)
p1.then(res => {
console.log(res)
}).catch(err => {
console.log('err:', err)
})
// 2.傳入實現then方法物件
const p2 = Promise.reject({
then: function(resolve, reject) {
resolve('aaaa')
}
})
p2.then(res => {
console.log(res)
}).catch(err => {
console.log('err:', err)
})
// 3.傳入Promise
const p3 = Promise.reject(new Promise((resolve, reject) => {
resolve('aaaa')
})
)
p3.then(res => {
console.log(res)
}).catch(err => {
console.log('err:', err)
})
6.3.all方法
all方法可以接收由多個Promise物件組成的陣列(準確來說是可接收一個可迭代物件),all方法呼叫返回的Promise狀態,由所有Promise物件共同決定。
-
當傳入的所有Promise物件的狀態都為fulfilled是,all方法返回的Promise狀態就為fulfilled,並且會將所有Promise物件的返回值組成一個陣列;
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(333) }, 3000) }) Promise.all([p1, p2, p3]).then(res => { console.log('res:', res) // res: [ 111, 222, 333 ] }).catch(err => { console.log('err:', err) })
-
當傳入的Promise有一個變成了rejected狀態,那麼就會獲取第一個變成rejected狀態的返回值作為all方法返回的Promise狀態;
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('err message') }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(333) }, 3000) }) Promise.all([p1, p2, p3]).then(res => { console.log('res:', res) }).catch(err => { console.log('err:', err) // err: err message })
6.4.allSettled方法
相比於all方法,allSettled方法不管傳入的Promise物件的狀態是fulfilled還是rejected,最終都會講結果返回,並且返回的結果是一個陣列,陣列中存放著每一個Promise對應的狀態status和對應的值value。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111)
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err message')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(333)
}, 3000)
})
Promise.allSettled([p1, p2, p3]).then(res => {
console.log('res:', res)
}).catch(err => {
console.log('err:', err)
})
6.5.race方法
race翻譯為競爭,顧名思義哪一個Promise物件最先返回結果,就使用最先返回結果的Promise狀態。
一下程式碼是p1
最先有結果的,p1
中執行的是resolve,所以返回的狀態為fulfilled:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111)
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err message')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(333)
}, 3000)
})
Promise.race([p1, p2, p3]).then(res => {
console.log('res:', res) // res: 111
}).catch(err => {
console.log('err:', err)
})
6.6.any方法
any方法是ES12中新增的方法,與race是類似的,any方法會等到有一個fulfilled狀態的Promise,才會決定any呼叫返回新Promise的狀態(也就是說any一定會等到有一個Promise狀態為fullfilled)。
那麼,如果所有的Promise物件的狀態都變為了rejected呢?最終就會報一個AggregateError
錯誤,如果想拿到所有的rejected狀態的返回值,可以通過在捕獲異常回撥引數中的errors
獲取:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err message1')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err message2')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err message3')
}, 3000)
})
Promise.any([p1, p2, p3]).then(res => {
console.log('res:', res)
}).catch(err => {
console.log(err)
console.log(err.errors)
})
注意:any方法是ES12新增的,node版本過低的話是會報錯找不到any方法的,可以在瀏覽器中測試。
7.手寫Promise
掌握了以上Promise的用法,那麼就一步步來實現一下Promise吧。
7.1.executor的實現
- 建立一個類,這個類可接收一個executor函式;
- executor函式需傳入兩個函式resolve和reject,並且executor是需要立即執行的;
- 建立三個常量用於管理Promise的三種狀態;
- 一旦Promise的狀態改變就不能再次被修改;
- 還需將傳入resolve和reject的引數值進行儲存,便於後續then的使用;
// 定義Promise的三種狀態常量
const PENDING_STATUS = 'pending'
const FULFILLED_STATUS = 'fulfilled'
const REJECTED_STATUS = 'rejected'
class MyPromise {
constructor(executor) {
// 初始化Promise的狀態為pending
this.promiseStatus = PENDING_STATUS
// 初始化變數,用於儲存resolve和reject傳入的引數值
this.value = undefined
this.reason = undefined
// 1.定義executor需要傳入的resolve函式
const resolve = (value) => {
// 只有當Promise的狀態為pending,才能將狀態改變fulfilled
if (this.promiseStatus === PENDING_STATUS) {
this.promiseStatus = FULFILLED_STATUS
this.value = value
console.log('呼叫了resolve,狀態變成fulfilled啦~')
}
}
// 2.定義executor需要傳入的reject函式
const reject = (reason) => {
// 只有當Promise的狀態為pending,才能將狀態改變為rejected
if (this.promiseStatus === PENDING_STATUS) {
this.promiseStatus = REJECTED_STATUS
this.reason = reason
console.log('呼叫了reject,狀態變成rejected啦~')
}
}
// 3.將定義的兩個函式傳入executor並呼叫
executor(resolve, reject)
}
}
簡單測試一下:
// 先呼叫resolve
new MyPromise((resolve, reject) => {
resolve()
reject()
})
// 先呼叫reject
new MyPromise((resolve, reject) => {
reject()
resolve()
})
7.2.then方法的實現
(1)then基本實現
- then方法接收兩個引數:
- onFulfilled回撥:當Promise狀態變為fulfilled需要執行的回撥;
- onRejected回撥:當Promise狀態變為rejected需要執行的回撥
class MyPromise {
constructor(executor) {
// 初始化Promise的狀態為pending
this.promiseStatus = PENDING_STATUS
// 初始化變數,用於儲存resolve和reject傳入的引數值
this.value = undefined
this.reason = undefined
// 1.定義executor需要傳入的resolve函式
const resolve = (value) => {
// 只有當Promise的狀態為pending,才能將狀態改變fulfilled
if (this.promiseStatus === PENDING_STATUS) {
// 新增微任務
queueMicrotask(() => {
// 如果Promise狀態不為pending,後面的程式碼就不再執行了
if (this.promiseStatus !== PENDING_STATUS) return
this.promiseStatus = FULFILLED_STATUS
this.value = value
// 狀態變成fulfilled就去呼叫onFulfilled
this.onFulfilled(this.value)
})
}
}
// 2.定義executor需要傳入的reject函式
const reject = (reason) => {
// 只有當Promise的狀態為pending,才能將狀態改變為rejected
if (this.promiseStatus === PENDING_STATUS) {
// 新增微任務
queueMicrotask(() => {
// 如果Promise狀態不為pending,後面的程式碼就不再執行了
if (this.promiseStatus !== PENDING_STATUS) return
this.promiseStatus = REJECTED_STATUS
this.reason = reason
// 狀態變成rejected就去呼叫onRejected
this.onRejected(this.reason)
})
}
}
// 3.將定義的兩個函式傳入executor並呼叫
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
// 儲存fulfilled和rejected狀態的回撥
this.onFulfilled = onFulfilled
this.onRejected = onRejected
}
}
注意:這裡將onFulfilled和onRejected的調動放在了queueMicrotask
,在JavaScript中可以通過queueMicrotask
使用微任務,而原Promise的then中回撥的執行,也是會被放在微任務中的,為什麼要放在微任務中呢?
原因:如果不使用微任務,那麼在executor中執行resolve或者reject時,then方法還沒被呼叫,onFulfilled和onRejected就都還沒被賦值,所以呼叫時會報錯,加入微任務就可以實現將onFulfilled和onRejected的呼叫推遲到下一次事件迴圈,也就是等then呼叫後賦值了才會執行。
簡單測試一下:
const p1 = new MyPromise((resolve, reject) => {
resolve('aaaa')
})
const p2 = new MyPromise((resolve, reject) => {
reject('err message')
})
p1.then(res => {
console.log(res) // aaaa
}, err => {
console.log(err)
})
p2.then(res => {
console.log(res)
}, err => {
console.log(err) // err message
})
(2)then優化一
- 對於以上then的基本實現,還存在一些不足之處,比如:
- then方法是可以進行多次呼叫的,並且每一次呼叫都是獨立呼叫,互不影響,所以需要收集當Promise狀態改變時,對應需要執行哪些回撥,需用陣列進行收集;
- 如果then是放到定時器中呼叫的,那麼改then的回撥是不會被呼叫的,因為在前面我們是通過將回撥新增到微任務中執行的,而定時器是巨集任務,會在微任務執行完成後執行,所以定時器中then的回撥就沒有被呼叫;
- 當then是放到定時器中執行的,那麼執行的時候,微任務已經執行完成了,Promise狀態肯定也確定下來了,那麼只需要直接呼叫then中的回撥即可;
class MyPromise {
constructor(executor) {
// 初始化Promise的狀態為pending
this.promiseStatus = PENDING_STATUS
// 初始化變數,用於儲存resolve和reject傳入的引數值
this.value = undefined
this.reason = undefined
// 初始化兩個陣列,分別用於儲存then中對應需要執行的回撥
this.onFulfilledFns = []
this.onRejectedFns = []
// 1.定義executor需要傳入的resolve函式
const resolve = (value) => {
// 只有當Promise的狀態為pending,才能將狀態改變fulfilled
if (this.promiseStatus === PENDING_STATUS) {
// 新增微任務
queueMicrotask(() => {
// 如果Promise狀態不為pending,後面的程式碼就不再執行了
if (this.promiseStatus !== PENDING_STATUS) return
this.promiseStatus = FULFILLED_STATUS
this.value = value
// 狀態變成fulfilled就去遍歷呼叫onFulfilled
this.onFulfilledFns.forEach(fn => {
fn(this.value)
})
})
}
}
// 2.定義executor需要傳入的reject函式
const reject = (reason) => {
// 只有當Promise的狀態為pending,才能將狀態改變為rejected
if (this.promiseStatus === PENDING_STATUS) {
// 新增微任務
queueMicrotask(() => {
// 如果Promise狀態不為pending,後面的程式碼就不再執行了
if (this.promiseStatus !== PENDING_STATUS) return
this.promiseStatus = REJECTED_STATUS
this.reason = reason
// 狀態變成rejected就去遍歷呼叫onRejected
this.onRejectedFns.forEach(fn => {
fn(this.reason)
})
})
}
}
// 3.將定義的兩個函式傳入executor並呼叫
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
// 1.如果在呼叫then時,Promise的狀態已經確定了,就直接執行回撥
if (this.promiseStatus === FULFILLED_STATUS && onFulfilled) {
onFulfilled(this.value)
}
if (this.promiseStatus === REJECTED_STATUS && onRejected) {
onRejected(this.reason)
}
// 2.如果呼叫then時,Promise的狀態還沒確定下來,就放入微任務中執行
if (this.promiseStatus === PENDING_STATUS) {
// 將then中成功的回撥和失敗的回撥分別存入陣列中
this.onFulfilledFns.push(onFulfilled)
this.onRejectedFns.push(onRejected)
}
}
}
簡單測試一下:
const p = new MyPromise((resolve, reject) => {
resolve('aaaa')
reject('err message')
})
p.then(res => {
console.log('res1:', res)
}, err => {
console.log('err1:', err)
})
p.then(res => {
console.log('res2:', res)
}, err => {
console.log('err1:', err)
})
setTimeout(() => {
p.then(res => {
console.log('res3:', res)
}, err => {
console.log('err1:', err)
})
})
(3)then優化二
- 通過上一步優化,then方法還存在一個缺陷,就是不能進行鏈式呼叫,在前面講then方法時,then方法執行的返回值是一個promise物件,並且返回的promise狀態是由then方法中回撥函式的返回值決定的,then中必定需要返回一個新的Promise;
- 上一個then中回撥的返回值可以傳遞到下一個then中成功的回撥中,也就是返回的promise執行了resolve方法,那麼什麼時候可以傳遞到下一個then中失敗的回撥中呢?只需要上一個then中丟擲異常即可,相當於返回的promise執行了reject方法;
- 所以,在這裡需要拿到then中回撥函式返回的結果,並且需要通過
try catch
判斷是呼叫resolve還是reject; - 注意:如果是在executor中就丟擲了異常,也需要通過
try catch
去執行executor;
class MyPromise {
constructor(executor) {
// 初始化Promise的狀態為pending
this.promiseStatus = PENDING_STATUS
// 初始化變數,用於儲存resolve和reject傳入的引數值
this.value = undefined
this.reason = undefined
// 初始化兩個陣列,分別用於儲存then中對應需要執行的回撥
this.onFulfilledFns = []
this.onRejectedFns = []
// 1.定義executor需要傳入的resolve函式
const resolve = (value) => {
// 只有當Promise的狀態為pending,才能將狀態改變fulfilled
if (this.promiseStatus === PENDING_STATUS) {
// 新增微任務
queueMicrotask(() => {
// 如果Promise狀態不為pending,後面的程式碼就不再執行了
if (this.promiseStatus !== PENDING_STATUS) return
this.promiseStatus = FULFILLED_STATUS
this.value = value
// 狀態變成fulfilled就去遍歷呼叫onFulfilled
this.onFulfilledFns.forEach(fn => {
fn(this.value)
})
})
}
}
// 2.定義executor需要傳入的reject函式
const reject = (reason) => {
// 只有當Promise的狀態為pending,才能將狀態改變為rejected
if (this.promiseStatus === PENDING_STATUS) {
// 新增微任務
queueMicrotask(() => {
// 如果Promise狀態不為pending,後面的程式碼就不再執行了
if (this.promiseStatus !== PENDING_STATUS) return
this.promiseStatus = REJECTED_STATUS
this.reason = reason
// 狀態變成rejected就去遍歷呼叫onRejected
this.onRejectedFns.forEach(fn => {
fn(this.reason)
})
})
}
}
// 3.將定義的兩個函式傳入executor並呼叫
// 如果executor中就丟擲了異常,那麼直接執行reject即可
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
// 1.如果在呼叫then時,Promise的狀態已經確定了,就直接執行回撥
if (this.promiseStatus === FULFILLED_STATUS && onFulfilled) {
// 通過try catch捕獲異常,沒有捕獲到執行resolve,捕獲到執行reject
try {
const value = onFulfilled(this.value)
resolve(value)
} catch (err) {
reject(err)
}
}
if (this.promiseStatus === REJECTED_STATUS && onRejected) {
try {
const reason = onRejected(this.reason)
resolve(reason)
} catch(err) {
reject(err)
}
}
// 2.如果呼叫then時,Promise的狀態還沒確定下來,就放入微任務中執行
if (this.promiseStatus === PENDING_STATUS) {
// 將then中成功的回撥和失敗的回撥分別存入陣列中
// 將傳入的回撥外包裹一層函式,目的是為了這裡能夠拿到then中回撥執行的結果
this.onFulfilledFns.push(() => {
try {
const value = onFulfilled(this.value)
resolve(value)
} catch(err) {
reject(err)
}
})
this.onRejectedFns.push(() => {
try {
const reason = onRejected(this.reason)
resolve(reason)
} catch(err) {
reject(err)
}
})
}
})
}
}
簡單測試一下:
const p = new MyPromise((resolve, reject) => {
resolve('aaaa')
})
p.then(res => {
console.log('res1:', res)
return 'bbbb'
}, err => {
console.log('err1:', err)
}).then(res => {
console.log('res2:', res)
throw new Error('err message')
}, err => {
console.log('err2:', err)
}).then(res => {
console.log('res3:', res)
}, err => {
console.log('err3:', err)
})
7.3.catch方法的實現
- catch方法的功能類似於then方法中的失敗回撥,所以,實現catch方法只需要呼叫then,給then傳入失敗的回撥即可;
- 只給then傳入一個回撥,意味著根據上面的程式碼,我們還需要對then的回撥進行條件判斷,有值才新增到對應陣列中;
- 注意:在then後鏈式呼叫catch會有一個問題,呼叫catch方法的promise是then執行之後返回的新promise,而catch真正需要去呼叫的是當前then的失敗回撥,而不是當前then執行後結果promise的失敗回撥,所以,可以將當前then的失敗回撥推到下一次的promise中,而丟擲異常就可以實現(因為上一個then丟擲異常,可以傳遞到下一個then的失敗回撥中)
// catch方法實現
catch(onRejected) {
return this.then(undefined, onRejected)
}
then方法改進:
簡單測試一下:
const p = new MyPromise((resolve, reject) => {
reject('err message')
})
p.then(res => {
console.log(res)
}).catch(err => {
console.log(err) // err message
})
7.4.finally方法的實現
- finally方法不管是Promise狀態變成fulfilled還是rejected都會被執行;
- 這裡可以巧妙的藉助then方法,不管then是執行成功的回撥還是失敗的回撥,都去執行finally中的回撥即可;
- 注意:如果在finally之前使用了catch,因為catch的實現也是去呼叫then,並且給then的成功回撥傳遞的是undefined,那麼執行到catch可能出現斷層的現象,導致不會執行到finally,也可以通過在then中新增判斷解決;
finally(onFinally) {
this.then(() => {
onFinally()
}, () => {
onFinally()
})
// 也可直接簡寫成:
// this.then(onFinally, onFinally)
}
then方法改進:
簡單測試一下:
const p = new MyPromise((resolve, reject) => {
resolve('aaaa')
})
p.then(res => {
console.log('res:', res) // res: aaaa
}).catch(err => {
console.log('err:', err)
}).finally(() => {
console.log('我是一定會執行的!') // 我是一定會執行的!
})
7.5.resolve和reject方法的實現
- resolve和reject類方法的實現就是去呼叫Promise中executor中的resolve和reject;
- 注意:類方法需要加上
static
關鍵字;
static resolve(value) {
return new MyPromise((resolve, reject) => resolve(value))
}
static reject(reasion) {
return new MyPromise((resolve, reject) => reject(reasion))
}
簡單測試一下:
MyPromise.resolve('aaaa').then(res => {
console.log(res) // aaaa
})
MyPromise.reject('bbbb').then(res => {
console.log(res)
}, err => {
console.log(err) // bbbb
})
7.6.all方法的實現
- all方法可接收一個promise陣列,當所有promise狀態都變為fulfilled,就返回所有promise成功的回撥值(一個陣列),當其中有一個promise狀態變為了rejected,就返回該promise的狀態;
- all實現的關鍵:當所有promise狀態變為fulfilled就去呼叫resolve,當有一個promise狀態變為rejected就去呼叫reject;
static all(promises) {
return new MyPromise((resolve, reject) => {
// 用於存放所有成功的返回值
const results = []
promises.forEach(promise => {
promise.then(res => {
results.push(res)
// 當成功返回值的長度與傳入promises的長度相等,就呼叫resolve
if (results.length === promises.length) {
resolve(results)
}
}, err => {
// 一旦有一個promise變成了rejected狀態,就呼叫reject
reject(err)
})
})
})
}
簡單測試一下:
const p1 = new MyPromise(resolve => {
setTimeout(() => {
resolve('aaaa')
}, 1000)
})
const p2 = new MyPromise(resolve => {
setTimeout(() => {
resolve('bbbb')
}, 2000)
})
const p3 = new MyPromise(resolve => {
setTimeout(() => {
resolve('cccc')
}, 3000)
})
MyPromise.all([p1, p2, p3]).then(res => {
console.log(res) // [ 'aaaa', 'bbbb', 'cccc' ]
}).catch(err => {
console.log(err)
})
7.7.allSettled方法的實現
- allSettled方法會返回所有promise的結果陣列,陣列中包含每一個promise的狀態和值;
- 不管promise的狀態為什麼,最終都會呼叫resolve;
static allSettled(promises) {
return new MyPromise((resolve, reject) => {
// 用於存放所有promise的狀態和返回值
const results = []
promises.forEach(promise => {
promise.then(res => {
results.push({ status: FULFILLED_STATUS, value: res })
// 當長度相等,呼叫resolve
if (results.length === promises.length) {
resolve(results)
}
}, err => {
results.push({ status: REJECTED_STATUS, value: err })
// 當長度相等,呼叫resolve
if (results.length === promises.length) {
resolve(results)
}
})
})
})
}
簡單測試一下:
const p1 = new MyPromise(resolve => {
setTimeout(() => {
resolve('aaaa')
}, 1000)
})
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('err message')
}, 2000)
})
const p3 = new MyPromise(resolve => {
setTimeout(() => {
resolve('bbbb')
}, 3000)
})
MyPromise.allSettled([p1, p2, p3]).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
7.8.race方法的實現
- race方法是獲取最先改變狀態的Promise,並以該Promise的狀態作為自己的狀態;
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
// 得到狀態最先改變的promise,呼叫對應的resolve和reject
promise.then(res => {
resolve(resolve)
}, err => {
reject(err)
})
})
})
}
7.9.any方法的實現
- any方法會等到有一個Promise的狀態變成fulfilled,最終就是fulfilled狀態;
- 如果傳入的所有Promise都為rejected狀態,會返回一個
AggregateError
,並且可以在AggregateError
中的errors
屬性中獲取所有錯誤資訊;
static any(promises) {
return new MyPromise((resolve, reject) => {
// 用於記錄狀態為rejected的值
const reasons = []
promises.forEach(promise => {
promise.then(res => {
// 當有一個promise變成fulfilled狀態就呼叫resolve
resolve(res)
}, err => {
reasons.push(err)
// 當所有promise都是rejected就呼叫reject,並且傳入AggregateError
if (reasons.length === promises.length) {
reject(new AggregateError(reasons))
}
})
})
})
}
簡單測試一下:
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('err message1')
}, 1000)
})
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('err message2')
}, 2000)
})
const p3 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('err message3')
}, 3000)
})
MyPromise.any([p1, p2, p3]).then(res => {
console.log(res)
}).catch(err => {
console.log('err:', err)
console.log(err.errors)
})
7.10.Promise手寫完整版整理
上面已經對Promise的各個功能進行了實現,下面就來整理一下最終的完整版,可以將一些重複的邏輯抽取出去,比如try catch。
// 定義Promise的三種狀態常量
const PENDING_STATUS = 'pending'
const FULFILLED_STATUS = 'fulfilled'
const REJECTED_STATUS = 'rejected'
// try catch邏輯抽取
function tryCatchFn(execFn, value, resolve, reject) {
try {
const result = execFn(value)
resolve(result)
} catch (err) {
reject(err)
}
}
class MyPromise {
constructor(executor) {
// 初始化Promise的狀態為pending
this.promiseStatus = PENDING_STATUS
// 初始化變數,用於儲存resolve和reject傳入的引數值
this.value = undefined
this.reason = undefined
// 初始化兩個陣列,分別用於儲存then中對應需要執行的回撥
this.onFulfilledFns = []
this.onRejectedFns = []
// 1.定義executor需要傳入的resolve函式
const resolve = (value) => {
// 只有當Promise的狀態為pending,才能將狀態改變fulfilled
if (this.promiseStatus === PENDING_STATUS) {
// 新增微任務
queueMicrotask(() => {
// 如果Promise狀態不為pending,後面的程式碼就不再執行了
if (this.promiseStatus !== PENDING_STATUS) return
this.promiseStatus = FULFILLED_STATUS
this.value = value
// 狀態變成fulfilled就去遍歷呼叫onFulfilled
this.onFulfilledFns.forEach(fn => {
fn(this.value)
})
})
}
}
// 2.定義executor需要傳入的reject函式
const reject = (reason) => {
// 只有當Promise的狀態為pending,才能將狀態改變為rejected
if (this.promiseStatus === PENDING_STATUS) {
// 新增微任務
queueMicrotask(() => {
// 如果Promise狀態不為pending,後面的程式碼就不再執行了
if (this.promiseStatus !== PENDING_STATUS) return
this.promiseStatus = REJECTED_STATUS
this.reason = reason
// 狀態變成rejected就去遍歷呼叫onRejected
this.onRejectedFns.forEach(fn => {
fn(this.reason)
})
})
}
}
// 3.將定義的兩個函式傳入executor並呼叫
// 如果executor中就丟擲了異常,那麼直接執行reject即可
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
// 判斷onRejected是否有值,沒有值的話直接賦值一個丟擲異常的方法,用於傳遞到下一次then中的失敗回撥,供catch呼叫
onRejected = onRejected || (err => {throw err})
// 判斷onFulfilled是否有值,避免在使用catch時傳入的undefined不會執行,出現斷層現象
onFulfilled = onFulfilled || (value => value)
return new MyPromise((resolve, reject) => {
// 1.如果在呼叫then時,Promise的狀態已經確定了,就直接執行回撥
if (this.promiseStatus === FULFILLED_STATUS) {
// 通過try catch捕獲異常,沒有捕獲到執行resolve,捕獲到執行reject
tryCatchFn(onFulfilled, this.value, resolve, reject)
}
if (this.promiseStatus === REJECTED_STATUS) {
tryCatchFn(onRejected, this.reason, resolve, reject)
}
// 2.如果呼叫then時,Promise的狀態還沒確定下來,就放入微任務中執行
if (this.promiseStatus === PENDING_STATUS) {
// 將then中成功的回撥和失敗的回撥分別存入陣列中
// 將傳入的回撥外包裹一層函式,目的是為了這裡能夠拿到then中回撥執行的結果
this.onFulfilledFns.push(() => {
tryCatchFn(onFulfilled, this.value, resolve, reject)
})
this.onRejectedFns.push(() => {
tryCatchFn(onRejected, this.reason, resolve, reject)
})
}
})
}
catch(onRejected) {
return this.then(undefined, onRejected)
}
finally(onFinally) {
this.then(onFinally, onFinally)
}
static resolve(value) {
return new MyPromise((resolve, reject) => resolve(value))
}
static reject(reasion) {
return new MyPromise((resolve, reject) => reject(reasion))
}
static all(promises) {
return new MyPromise((resolve, reject) => {
// 用於存放所有成功的返回值
const results = []
promises.forEach(promise => {
promise.then(res => {
results.push(res)
// 當成功返回值的長度與傳入promises的長度相等,就呼叫resolve
if (results.length === promises.length) {
resolve(results)
}
}, err => {
// 一旦有一個promise變成了rejected狀態,就呼叫reject
reject(err)
})
})
})
}
static allSettled(promises) {
return new MyPromise((resolve, reject) => {
// 用於存放所有promise的狀態和返回值
const results = []
promises.forEach(promise => {
promise.then(res => {
results.push({ status: FULFILLED_STATUS, value: res })
// 當長度相等,呼叫resolve
if (results.length === promises.length) {
resolve(results)
}
}, err => {
results.push({ status: REJECTED_STATUS, value: err })
// 當長度相等,呼叫resolve
if (results.length === promises.length) {
resolve(results)
}
})
})
})
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
// 得到狀態最先改變的promise,呼叫對應的resolve和reject
promise.then(resolve, reject)
})
})
}
static any(promises) {
return new MyPromise((resolve, reject) => {
// 用於記錄狀態為rejected的值
const reasons = []
promises.forEach(promise => {
promise.then(res => {
// 當有一個promise變成fulfilled狀態就呼叫resolve
resolve(res)
}, err => {
reasons.push(err)
// 當所有promise都是rejected就呼叫reject,並且傳入AggregateError
if (reasons.length === promises.length) {
reject(new AggregateError(reasons))
}
})
})
})
}
}