本篇文章是《包教包會,和你實現一個Promise》的第二篇,緊接着第一篇的內容。因此,若是你尚未看過第一篇,你可能須要看一下包教包會,和你實現一個Promise(一)。javascript
第一篇咱們已經實現了一個形式上很是相似Promise的MyPromise,可是儘管使用形式有點像,它離徹底符合Promise A+規範的Promise還差的遠。如今的MyPromise:java
function MyPromise(executor) {
this.status = 'pending'
this.data = undefined
this.reason = undefined
this.resolvedCallbacks = []
this.rejectedCallbacks = []
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.data = value
this.resolvedCallbacks.forEach(fn => fn(this.data))
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.rejectedCallbacks.forEach(fn => fn(this.reason))
}
}
executor(resolve, reject)
}
MyPromise.prototype.then = function(onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
this.resolvedCallbacks.push(onResolved)
this.rejectedCallbacks.push(onRejected)
}
if (this.status === 'fulfilled') {
onResolved(this.data)
}
if (this.status === 'rejected') {
onRejected(this.reason)
}
}
複製代碼
如今這個MyPromise的問題在於,它沒法進行鏈式調用。咱們在使用Promise的時候,會有這樣的代碼:promise
let promise1 = new Promise(function(resolve, reject) {
// 模擬異步
setTimeout(() => {
let flag = Math.random() > 0.5 ? true : false
if (flag) {
resolve('success')
} else {
reject('fail')
}
}, 1000)
})
promise1.then(res => {
return 1
}, err => {
return err
}).then(res => {
console.log(res)
})
複製代碼
可是目前MyPromise的then是一次性,執行完了就完了,沒有返回能then的東西。瀏覽器
根據Promise A+規範,一個Promise實例在then以後必定會返回一個新的Promise實例,這樣就可使用then來實現鏈式調用了。app
在實現then方法以前,咱們先簡單聊一下實現鏈式調用的技巧。通常來講,實現鏈式調用有兩個方法:dom
function $(selector) {
return new jQuery(selector)
}
class jQuery {
constructor(selector) {
let slice = Array.prototype.slice
this.domArray = slice.call(document.querySelectorAll(selector))
// ...
}
append() {
// ...
return this
}
addClass() {
//..
return this
}
}
複製代碼
上面代碼思路:先構造一個jQuery對象,它的某些方法在執行完了以後返回this,也就是這個一開始構造的jQuery對象。這樣,就能夠這樣鏈式調用下去:$('.app').append($span).addClass('test')
異步
class Promise {
constructor() {
//...
}
then() {
//...
let promise2 = new Promise() // 重點在這
return promise2
}
}
// 使用
let promise1 = new Promise()
let promise2 = promise1.then()
let promise3 = promise2.then()
// 也能夠直接寫成這樣
promise1.then().then()
複製代碼
這樣,每次then方法執行的時候,都會返回一個徹底新的Promise實例,就能夠繼續往下then了。函數
到這裏,咱們終於來到了Promise最核心部分,那就是then方法。在整個Promise A+規範裏,大部份內容都在說then是如何實現的,說then方法是Promise的核心一點問題都沒有。post
咱們先明確then方法的功能:測試
接下來分開解釋。
第一點,實現鏈式調用的方法,上面講過了,再也不贅述。
第二點,then方法返回的新Promise實例狀態依賴於當前實例狀態和回調返回值的狀態。要理解這句話,請仔細看下面的例子:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
let promise2 = promise1.then(res => {
return res
})
console.log(promise1) // pendding狀態
console.log(promise2) // pending狀態
setTimeout(() => {
console.log(promise1) // 成功狀態
console.log(promise2) // 成功狀態
}, 1000)
複製代碼
從上面的代碼能夠看出:若是promise1是pending狀態,那promise2也必定是pending狀態。只有當promise1狀態肯定,promise2的狀態纔有可能肯定。 那當promise1狀態肯定的時候,是否是promise2的狀態就肯定了呢?也不是,還要看promise1的then裏註冊的兩個函數的返回值。 請看下面的例子:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
let promise2 = promise1.then(res => {
return Promise.reject('拒絕') // 注意這裏,這個回調返回了一個拒絕狀態Promise哦
})
console.log(promise1) // pending狀態
console.log(promise2) // pending狀態
setTimeout(() => {
console.log(promise1) // 成功狀態
console.log(promise2) // 這裏是失敗狀態
}, 1000)
複製代碼
從上面的兩個例子,咱們能夠獲得如下結論:當咱們在then方法裏構造新的Promise時,咱們不只要根據當前Promise實例的狀態使用不一樣的策略,同時還要考慮當前then方法傳遞的兩個回調的結果。
第三點,其實很好理解,當promise1的then方法裏傳遞的回調執行的結果,能夠被下個實例拿到,這裏很簡單,得到回調的結果,再根據條件resolve或者reject傳進去便可。
以上的內容,若是聽不懂不要緊,下面講then方法具體實現的時候還會再說道。
then方法須要返回一個新的Promise實例,並且須要根據當前實例的狀態來構造這個新的實例。因此MyPromise的then方法的代碼改爲以下的樣子:
MyPromise.prototype.then = function(onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
})
// this.resolvedCallbacks.push(onResolved)
// this.rejectedCallbacks.push(onRejected)
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
})
// onResolved(this.data)
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
})
// onRejected(this.reason)
}
return promise2
}
複製代碼
聲明一個須要返回的promise2,而後根據當前當前實例的狀態來給它賦值。在這裏,咱們先註釋了每一個if判斷裏原來的代碼。接下來的重點就是構造promise2,看它的executor函數具體如何實現了。 咱們先開討論。
根據以前的討論,當前爲pending時,須要等到它肯定時,promise2的狀態纔有可能肯定。因此MyPromise裏這部分代碼這樣寫
MyPromise.prototype.then = function(onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
// 聲明一個成功函數
function successFn(value) {
let x = onResolved(value)
}
// 聲明一個失敗函數
function failFn(reason) {
let x = onRejected(reason)
}
// 將成功函數push到當前實例的resolvedCallbacks
this.resolvedCallbacks.push(successFn)
// 將失敗函數push到當前實例的rejectedCallbacks
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
})
// onResolved(this.data)
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
})
// onRejected(this.reason)
}
return promise2
}
複製代碼
解釋一下上面代碼的意思:若是當前Promise的then調用時狀態爲pending時,咱們聲明successFn和failFn,而且把它們分別push到resolvedCallbacks和rejectedCallbacks裏。這樣,successFn和failFn的執行時機就交給了當前promise實例。噹噹前Promise實例狀態肯定時,successFn或者failFn就會被執行,這樣就能夠經過調用onResovled或者onRejected拿到回調的結果了。
這一步很是關鍵,若是當前Promise實例狀態爲pending時,then方法裏返回新的promise2就必須等到它狀態肯定時才能拿到它成功或者失敗回調的值。而後根據回調執行後的結果x來肯定promise2的狀態。
整個流程的順序以下:
if(this.status === 'pending')
分支裏fulfilled
或者rejected
這個簡單,直接調用onResovled或者onRejected,也就是當前實例then傳遞的第一個和第二個參數。
MyPromise.prototype.then = function(onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
let x = onResolved(value)
}
function failFn(reason) {
let x = onRejected(reason)
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
// 由於此時當前實例的resolve或者reject已經執行
// this.data或者this.reason
let x = onResolved(this.data)
})
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
// 此時當前實例resolve或者reject已經執行
let x = onRejected(this.reason)
})
}
return promise2
}
複製代碼
此時,當前實例的resolve或者reject已經執行,狀態已經肯定,this.data或者this.reason已經有值,直接用then傳遞的onResoved或者onRjected調用便可獲取x。
咱們的then寫到這裏,promise2它已經拿到了當前實例的回調結果了。咱們看一下實際使用中它在哪裏 。請看下面的例子:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
function onResolved(res) {
// 這裏進行處理
return xxx // 這裏返回的xxx其實就是上面代碼裏的x
}
function onRejected(err) {
// 處理
return xxx // 這裏返回的xxx就是上面代碼裏的x
}
let promise2 = promise1.then(onResolved, onRjected)
複製代碼
到這裏,當then方法執行時,咱們已經成功拿到了當前實例的回調值x,接下來,咱們將對這個值進行統一處理,並根據x來調用promise2的構造時的resolve或者reject方法來肯定promise2的狀態。
在規範裏,關於如何處理x來肯定promise2的狀態有一個專門的章節來論述。它在規範的2.3節,稱爲The Promise Resolution Procedure ,它根據x的可能的值進行鍼對處理。x能夠是如下的值:
咱們須要寫一個函數resolve_promise
來對x進行處理,並肯定promise2的狀態
MyPromise.prototype.then = function(onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
let x = onResolved(value)
// 注意這裏
resolve_promise(promise2, x, resolve, reject)
}
function failFn(reason) {
let x = onRejected(reason)
// 這裏也有
resolve_promise(promise2, x, resolve, reject)
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
let x = onResolved(this.data)
// 這裏也有哦
resolve_promise(promise2, x, resolve, reject)
})
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
// 還有這裏哦
let x = onRejected(this.reason)
resolve_promise(promise2, x, resolve, reject)
})
}
return promise2
}
// 這裏是resolve_promise
function resolve_promise(promise2, x, resolve, reject) {
}
複製代碼
要寫的resolve_promise接收4個參數,一個是當前正在構造的promise2實例,一個是經過當前回調拿到的結果x,resolve和reject是用來肯定promise2狀態的兩個方法,由於只有resolve或者reject被調用了,promise2的狀態才能肯定嘛 。
接下來,咱們根據x可能的四種狀態,來分別處理,這些都是規範的具體內容:
這種狀況只有在當前實例是pending情況下才有可能發生,例子以下:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
function onResolved(res) {
return promise2
}
let promise2 = promise1.then(onResolved)
promise2.then(res => {
console.log(res)
}, err => {
console.log(err)
// 這裏會打印出 TypeError: Chaining cycle detected for promise
})
複製代碼
因此當這種狀況發生時,根據規範,直接把promise2使用reject拒絕,並傳一個TypeError
function resolve_promise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('Chaining cycle detected for promise'))
return //這裏return不用再往下了
}
}
複製代碼
使用場景是這種狀況:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
function onResolved(res) {
// 這裏返回的promise就是咱們要處理的x
return new Promise(function(resolve, reject) {
reject('fail')
})
}
let promise2 = promise1.then(onResolved)
複製代碼
這個時候根據規範,promise2的狀態和值使用x的狀態和值,分三種狀況:
此時的resolve_promise方法進行以下更改:
function resolve_promise(promise2, x, resolve, reject) {
// x和promise2引用相同的狀況
if (x === promise2) {
reject(new TypeError('Chaining cycle detected for promise'))
return
}
// 若是x是MyPromise的實例
if (x instanceof MyPromise) {
x.then(function (v) {
resolve_promise(promise2, v, resolve, reject)
}, function (t) {
reject(t)
}
return
}
}
複製代碼
這裏你可能看不懂,不是說有三種狀況嗎?這個代碼也沒有針對這三種狀況作判斷呀。是這樣的,規範上說的三種狀況我經過一個x.then
就能夠拿到x構造時resolve或者reject函數傳遞的值。這裏真正的坑點在於,x在構造時使用resolve或者reject傳值時也可能傳遞了一個promise實例,並且仍是規範其它實現(好比bluebird或者Q)的promise實例,因此這裏才須要使用遞歸。這個坑點在規範裏沒說,可是若是不這樣寫,有不少測試用例通不過。
根據規範:
let then = x.then
,若是在這個過程出現拋出了異常,就reject(e)
then
是一個函數
reject(r)
resolve(x)
你可能看不懂規範上說的這些空間是幹嗎的,其實它是對Promise實現方案作的一個兼容處理。咱們知道,Promise並無官方實現,只有規範和測試用例,它有多種實現,好比bluebird和Q,若是我在使用時,同時用了bluebird和Q,就須要作兼容處理。
這裏的x就多是bluebird或者Q的實例。
那我如何判斷它是其它Promise的實現呢?看它有沒有一個then方法,全部Promise實現都有合乎規定的then方法。若是有then就會走到這裏的邏輯。若是x是一個包含then方法的普通對象,也會走到這裏。因此這裏纔會有這麼多的判斷,還有遞歸。
直接寫MyPromise:
function resolve_promise(promise2, x, resolve, reject) {
// x就是promise2的狀況
if (x === promise2) {
reject(new TypeError('Chaining cycle detected for promise'))
return
}
// x是MyPromise實例的狀況
if (x instanceof MyPromise) {
x.then(function(v) {
resolve_promise(promise2, v, resolve, reject)
}, function(t) {
reject(t)
})
return
}
// x 是對象或者函數
if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
// 開關
// 控制resolvePromise和rejectPromise還有catch裏reject的調用
let called = false
try { // x.then可能有異常,須要捕獲
let then = x.then
if (typeof then === 'function') {
// 有then方法,則調用,若是then方法並無實際resolvePromise
// 或者rejectPromise參數的話,promise2永遠都是pending狀態
// 由於resolve和reject永遠都不可能執行
then.call(x, function resolvePromise(y) {
if (called) return
called = true
resolve_promise(promise2, y, resolve, reject)
}, function rejectPromise(r) {
if (called) return
called = true
reject(r)
})
} else {
// 若是then不是一個函數直接resolve
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
複製代碼
看到這裏,你就能夠找出Promise的一個"bug"了,請看下面場景的代碼:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success')
}, 1000)
})
let promise2 = promise1.then(function(res) {
// 這裏返回一個包括then方法的對象
// 可是這個then方法啥都沒作
// 致使promise2在構造過程當中resolve或者reject永遠都沒執行
return {
then: function() {}
}
})
promise2.then(res => {
// 這裏永遠都不會執行
console.log(res)
})
// promise2永遠都是pending狀態
console.log(promise2)
複製代碼
你能夠把上面的代碼粘到瀏覽器裏試一下~
那直接resolve(x)
便可,在上面已經有了。
請看咱們已經寫的then函數裏:
MyPromise.prototype.then = function (onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
let x = onResolved(value)
resolve_promise(promise2, x, resolve, reject)
}
function failFn(reason) {
let x = onRejected(reason)
resolve_promise(promise2, x, resolve, reject)
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
let x = onResolved(this.data)
// 看這裏,看下面這行
resolve_promise(promise2, x, resolve, reject)
})
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
let x = onRejected(this.reason)
// 看這裏,看下面這行
resolve_promise(promise2, x, resolve, reject)
})
}
return promise2
}
複製代碼
在this.status === 'fulfilled'
和this.status === 'rejected'
這兩個分支裏執行resolve_promise
是拿不到promise2這個實例的,由於它沒還構造完成,是undefined ,因此這裏要加個setTimeout才行。並且,根據規範,onResolved和onRejected必須異步執行呢。
注意,this.stauts === 'pending'
的那個不用哦,由於它執行的時候就是異步的。
改爲下面這樣:
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
let x = onResolved(this.data)
resolve_promise(promise2, x, resolve, reject)
})
})
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
let x = onRejected(this.reason)
resolve_promise(promise2, x, resolve, reject)
})
})
}
複製代碼
其實,如今咱們已經基本寫出一個Promise了!可是你能夠會問,Promise.all
Promise.race
實例上的catch
等方法如今尚未啊!不用擔憂,只要完成了then,一個Promise就完成了百分之八十了,以上常的的幾個API都是小意思,要實現分分鐘~ 固然,它還有點小瑕疵,這些問題,還有測試,咱們下篇文章完成!
完成的代碼在這裏:
function MyPromise(executor) {
this.status = 'pending'
this.data = undefined
this.reason = undefined
this.resolvedCallbacks = []
this.rejectedCallbacks = []
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.data = value
this.resolvedCallbacks.forEach(fn => fn(this.data))
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.rejectedCallbacks.forEach(fn => fn(this.reason))
}
}
executor(resolve, reject)
}
MyPromise.prototype.then = function (onResolved, onRejected) {
let promise2
if (this.status === 'pending') {
promise2 = new MyPromise((resolve, reject) => {
function successFn(value) {
let x = onResolved(value)
resolve_promise(promise2, x, resolve, reject)
}
function failFn(reason) {
let x = onRejected(reason)
resolve_promise(promise2, x, resolve, reject)
}
this.resolvedCallbacks.push(successFn)
this.rejectedCallbacks.push(failFn)
})
}
if (this.status === 'fulfilled') {
promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
let x = onResolved(this.data)
resolve_promise(promise2, x, resolve, reject)
})
})
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
let x = onRejected(this.reason)
resolve_promise(promise2, x, resolve, reject)
})
})
}
return promise2
}
function resolve_promise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('Chaining cycle detected for promise'))
return
}
if (x instanceof MyPromise) {
x.then(function(v) {
resolve_promise(promise2, v, resolve, reject)
}, function(t) {
reject(t)
})
return
}
if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
let called = false
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, function resolvePromise(y) {
if (called) return
called = true
resolve_promise(promise2, y, resolve, reject)
}, function rejectPromise(r) {
if (called) return
called = true
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
複製代碼
感謝您的閱讀!