如何实现 Promise?

507 阅读5分钟

为何需要 promise ?

在 promise 出现之前,异步编程由回调函数完成,很容易出现回调嵌套过多,也即常提到的“回调地狱”。回调地狱不仅是可读性差,维护起来也相当麻烦,如果某个环节出错了,经常无法准确定位问题。

Promise 正是为了解决这些问题而出现,链式调用解决了回调地狱的问题,它的错误传播机制实现了统一的错误信息处理。可以看一个 MDN 上的例子

// 回调地狱
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 的调用方式搭建它的构造函数:

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 中的回调函数会不会执行?

let p1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000)
})
// 回调会执行吗?
p1.then((v) => {
    console.log('success')
})

例子中的 resolve 函数是异步执行的,而 then 是同步执行的,也就是说 then 会先于 resolve 执行,那 then 执行的时候 Promise 的状态还是 Pending。所以,then 中的回调函数是不会执行的。接下来我们做一些修改来解决这个问题:

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 函数的参数传入。

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) 方法中,我们接下来结合规范来看它的执行过程 :

  • 如果 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)
	}
}