如何實現 Promise?

爲什麼須要 promise ?

在 promise 出現以前,異步編程由回調函數完成,很容易出現回調嵌套過多,也即常提到的「回調地獄」。回調地獄不只是可讀性差,維護起來也至關麻煩,若是某個環節出錯了,常常沒法準肯定位問題。編程

Promise 正是爲了解決這些問題而出現,鏈式調用解決了回調地獄的問題,它的錯誤傳播機制實現了統一的錯誤信息處理。能夠看一個 MDN 上的例子promise

// 回調地獄
doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

// Promise 方案
doSomething()
.then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
複製代碼

動手實現

因爲 ES6 中的 Promise 聽從 Promise A+ 規範,接下來就根據其主要的幾個規則,開始一塊兒動手實現吧!異步

咱們先根據 Promise 的調用方式搭建它的構造函數:ide

class Promise {
    constructor (excutor) {
        this.value = null
        this.reason = null
		const resolve = (value) => {
            this.value = value
        }
        const reject = (reason) => {
            this.reason = reason
        }
        
        try {
            excutor(resolve, reject)
        } catch (reason) {
            reject(reason)
        }
    }
}

複製代碼
增長狀態機

每個 Promise 實例只能有三個狀態:pendingfulfilledrejected,且狀態之間的轉換隻能是 pending => fulfilledpending => rejected 。咱們接着實現這個功能:異步編程

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
    constructor (excutor) {
        this.value = null
        this.reason = null
        this.state = PENDING
		const resolve = (value) => {
            if (this.state === PENDING) {
	            this.value = value
                this.state = FULFILLED
            }
        }
        const reject = (reason) => {
            if (this.state === PENDING) {
	            this.reason = reason
    			this.state = REJECTED       
            }
        }
        
        try {
            excutor(resolve, reject)
        } catch (reason) {
            reject(reason)
        }
    }
}
複製代碼
實現 then 方法

Promise 中的 then 方法接受兩個函數做爲參數,分別是 Promise 成功和失敗的回調。因此在 then 方法中,先判斷 Promise 的狀態,若是是成功,就將 this.value 傳入回調並執行;若是失敗,將 this.reason 傳入回調並執行(因此須要全局的 valuereason)。函數

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
    constructor (excutor) {
		...
    }
    
    then (onFulfilled, onRejected) {
        if (this.state === FULFILLED) {
            onFullfilled(this.value)
        } else {
            onRejected(this.reason)
        }
    }
}
複製代碼
實現異步調用

目前咱們的實現只是同步的,思考一下下面例子中 then 中的回調函數會不會執行?ui

let p1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000)
})
// 回調會執行嗎?
p1.then((v) => {
    console.log('success')
})
複製代碼

例子中的 resolve 函數是異步執行的,而 then 是同步執行的,也就是說 then 會先於 resolve 執行,那 then 執行的時候 Promise 的狀態仍是 Pending。因此,then 中的回調函數是不會執行的。接下來咱們作一些修改來解決這個問題:this

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
    constructor (excutor) {
        this.value = null
        this.reason = null
        this.state = PENDING
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
		const resolve = (value) => {
            if (this.state === PENDING) {
	            this.value = value
                this.state = FULFILLED
                this.onFulfilledCallbacks.forEach(onFulfilledCallback => {
                    onFulfilledCallback()
                })
            }
        }
        const reject = (reason) => {
            if (this.state === PENDING) {
	            this.reason = reason
    			this.state = REJECTED
                this.onRejectedCallbacks.forEach(onRejectedCallback => {
                    onRejectedCallback()
                })
            }
        }
        
        try {
            excutor(resolve, reject)
        } catch (reason) {
            reject(reason)
        }
    }
    
    then (onFulfilled, onRejected) {
        if (this.state === FULFILLED) {
            this.onFulfilledCallbacks.push(() => {
                onFulfilled(this.value)
            })
        } else {
            this.onRejectedCallbacks.push(() => {                            	onRejected(this.reason)
            })
        }
    }
}
複製代碼
實現鏈式調用

在 Promise A+ 規範中,then 函數是能夠鏈式調用的,也就是說 then 函數的返回值也是一個 Promise,且這個 Promiseresolve 值是上一個 PromiseonFulfilled 函數的返回值,或 onRejected 函數的返回值。而若是上一個 Promise 的執行過程當中發生錯誤,那麼這個錯誤將被做爲返回的 Promise 的 ``onRejected 函數的參數傳入。spa

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
  ...    
  resolvePromise (promise, x, resolve, reject) {
    if (promise === x) {
        reject(new TypeError('Chaining cycle'))
        return
    }
    if (x instanceof APromise) {
      if (x.state === PENDING) {
        x.onFulfilledCallbacks.push(() => {
          resolve(x.value)
        })
      } else if (x.state === FULFILLED) {
        resolve(x.value)
      } else if (x.state === REJECTED) {
        reject(x.reason)
      }
    } else if (x && typeof x === 'object' || typeof x === 'function') {
        let used
        try {
          let then = x.then
          if (typeof then === 'function') {
            then.call(x, y => {
              if (used) return
              used = true
              this.resolvePromise(promise, y, resolve, reject)
            }, r => {
              if (used) return
              used = true
              reject(r)
            })
          } else {
            if (used) return
            used = true
            resolve(x)
          }
      } catch (reason) {
        if (used) return
        used = true
        reject(reason)
      }
    } else {
      resolve(x)
    }
  }
  then (onFulfilled, onRejected) {
    const promise2 = new Promise((resolve, reject) => {
      if (this.state === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          try {
              let x = onFulfilled(this.value)
              this.resolvePromise(promise2, x, resolve, reject)
          } catch (reason) {
              reject(reason)
          }
        })
        this.onRejectedCallbacks.push(() => {
          if (typeof onRejected !== 'function') {
            reject(this.reason)
          } else {
            try {
              let x = onRejected(this.reason)
              this.resolvePromise(promise2, x, resolve, reject)
            } catch (reason) {
              reject(reason)
            }
          }  
        })
      } else if (this.state === FULFILLED) {
        try {
          let x = onFulfilled(this.value)
          this.resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      } else {
        try {
          let x = onRejected(this.reason)
          this.resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      }
    })
    return promise2
  }
}
複製代碼

咱們已經知道 then 方法會返回一個 Promise 對象,且這個對象的 resolve 值是前一個 PromiseonFulfilled 函數的返回值,或 onRejected 函數的返回值。可是當返回值是 thenable 對象,或是 Promise 時,須要有特殊處理。具體的邏輯處理在 resolvePromise(promise, x, resolve, reject) 方法中,咱們接下來結合規範來看它的執行過程 :code

  • 若是 promisex 是同一個對象,那拋出一個 Chaining cycleTypeError
  • 若是 x 是一個 Promise 對象,那麼 promise 將使用 x 的狀態
    • x 處於 PENDING 狀態,promise 也將保持 PENDING 狀態直到 xresolvereject
    • x 處於 FULFILLED 狀態,用 xvalueresolve promise
    • x 處於 REJECTED 狀態,用一樣的 reasonreject promise
  • 若是 x 是一個對象或函數,將 then 指向 x.then
    • 若是 then 是一個函數,那調用它,並將 x 做爲它的 this ,以 resolvePromise 做爲第一個參數,rejectPromise 做爲第二個參數,且:
      • 若是 resolvePromise 以一個 y 值被調用了,則執行 resolvePromise(promise, x, resolve, reject)
      • 若是 resolvePromise 以一個 r 值被拒絕了,則執行 reject(r)
      • 若是 resolvePromiserejectPromise 都被調用了,或者某個已被被屢次調用了,則首次發生的調用生效,其他全部調用都被忽略。
      • 若是在 then 調用過程當中發生了錯誤,若是 resolvePromiserejectPromise 都沒被調用過,則 reject 這個錯誤;不然忽略這個錯誤
    • 若是 then 不是函數,則以 xresolve promise
  • 上述過程當中,若是發生異常,用異常來 reject promise
實現 catch 的異常處理

考慮這個場景:promisereject 且後續的 then 方法沒有傳入 onRejected 處理函數,那麼根據 then 中的處理邏輯,第二個 promisereason 就是前一個promisereason 值。因此 throw 方法中只要調用 this.then(null, onRejected) 就能處理第一個 promise 未被處理的 reject

class Promise {
	...
	throw (onRejected) {
		return this.then(null, onRejected)
	}
}
複製代碼
相關文章
相關標籤/搜索