Kaier33

Implementing Promise Using JavaScript

November 11, 2020 • ☕️ 7 min read

Promise是异步编程的一种解决方案, 在Promise出现之前, 我们经常能看(写)到类似如下代码, 也就是所谓的”回调地狱” :

step(1, function() {
  step(2, function() {
    step(3, function() {
      // more and more nesting
    })
  })
})

用Promise改写后, 可读性得到明显提高. 感觉像是在写同步代码.

Promise.resolve(1)
  .then(step => ++step)
  .then(step => ++step)
  .then(step => ++step)

先从实现一个简易版的Promise开始

// 根据Promise/A+对status的定义, 我们先声明其三个状态: 
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
  constructor(executor) {
    const self = this
    self.status = PENDING   // 默认为 pending
    self.value = undefined  // 完成态用来传递的值
    self.reason = undefined // 拒绝态用来传递的值

    // 当executor是异步执行时, 先把then的回调函数各自先压入对应的栈
    self.onFulfilledCallback = [] // 完成态的回调队列
    self.onRejectedCallback = []  // 拒绝态的回调队列

    // promise的完成态回调
    let _resolve = (value) => {
      // 状态只能变更一次, 防止后续执行重复调用的 resolve 或 reject
      if (self.status === PENDING) {
        self.status = FULFILLED   // 将status改为 完成态
        self.value = value        // 保存传递进来的value值
        self.onFulfilledCallback.forEach(fn => fn(self.value)) // 触发之前异步执行时入栈的函数
      }
    }

    // promise的拒绝态回调, 实现原理同上
    let _reject = (reason) => {
      if (self.status === PENDING) {
        self.status = REJECTED
        self.reason = reason
        self.onRejectedCallback.forEach(fn => fn(self.reason))
      }
    }

    // executor 就是我们在new Promise时传入的那个函数, eg: new Promise((resolve, reject) => {})
    try {
      executor(_resolve, _reject)
    } catch (err) {
      reject(err)
    }
  }

  // promise的 then 方法,用来获取当前或最终的 value 或 reason
  then(onFulfilled, onRejected) {
    const self = this
    if (self.status === FULFILLED) { // 当状态为成功态时, 返回value值
      onFulfilled(self.value)
    }
    if (self.status === REJECTED) { // 当状态为拒绝态时, 返回reason值
      onRejected(self.reason)
    }
    if (self.status === PENDING) { // 当executor是在异步函数中触发时, 先让then的两个函数入栈
      self.onFulfilledCallback.push(onFulfilled)
      self.onRejectedCallback.push(onRejected)
    }
  }
}

测试一下代码

const p1 = new MyPromise(resolve => resolve('FULFILLED')) 
const p2 = new MyPromise((_, reject) => reject('REJECTED'))
const p3 = new MyPromise(resolve => setTimeout(() => resolve('async'), 1000))

p1.then(data => console.log('resolve::', data))        // 控制台输出-> resolve:: FULFILLED
p2.then(_ => {}, err => console.log('reject::', err))  // 控制台输出-> reject:: REJECTED
p3.then(data => console.log('异步返回::', data))        // 1秒后控制台输出-> 异步返回:: async

加强版promise (then的链式调用 和 值穿透特性)

// 解析X. 根据X是promise还是简单类型还是引用类型去做不同处理
const resolvePromise = (promise2, x, resolve, reject) => {
  if (promise2 === x) { // 防止promise等待promise, 直接抛出错误
    return reject(new TypeError("Chaining cycle detected for promise"))
  }
  if ((x !== null && typeof(x) === 'object') || typeof(x) === 'function') {
    // x很有可能是个promise, 根据规范 2.3.3.1  定义一个then变量,用于检测x是否为promise
    try {
      let then = x.then
      if (typeof(then) === 'function') { // 如果有then函数, 则认为他是promise
        // 规范 2.3.3.2 确保this指向x
        then.call(x, (y) => { // y 有可能也是一个promise
          resolvePromise(promise2, y, resolve, reject) // 递归解析
        }, (r) => {
          reject(r)
        })
      } else { // 当前的x就是一个普通的引用类型 eg: x = {a: 233}
        resolve(x)
      }
    } catch (e) {
       reject(e)
    }
  } else { // 简单类型直接返回 eg: x = 233
    resolve(x)
  }
}

class MyPromise {
  // ...  相同的代码省略
  // 主要还是对then进行改写
  then(onFulfilled, onRejected) {
    // 为了能使其支持穿透调用 eg: p.then().then().then(), 确保其必须是一个函数
    onFulfilled = typeof(onFulfilled) === 'function' ? onFulfilled : data => data 
    onRejected = typeof(onRejected) === 'function' ? onRejected : err => {throw err}
    const self = this
    // 1) 返回一个新的promise用于支持链式调用
    // 2) 为了能让新的promise能拿到当前then方法 成功或失败执行后的结果. 先用一个变量存起来.
    //    当我们的上一个promise 执行 成功/失败 的状态后. 然后变返回新的promise的then (注意, 上一个promise记得要return value 出去, 这样下一个then才能接收到值)
    const promise2 = new MyPromise(function(resolve, reject) {
      if (self.status === FULFILLED) {
        // 为了能让promise2 拿到值, 必须在实例化完成之后 (注意, 这里用setTimeout其实并不太合适(变为宏任务), 这里只是为了兼容在浏览器和node端运行, 才用此方法, 推荐用queueMicrotask)
        setTimeout(() => {
          try { // 捕获在onFulfilled的运行报错
            let x = onFulfilled(self.value) // 注意. x不一定是常量. 也有可能是promise
            resolvePromise(promise2, x, resolve, reject) // 解析传过来的x是常量还是promise
          } catch (error) {
            reject(error) // 如果执行函数时抛出失败, 那么会走向下一个人的失败
          }
        }, 0)
      }
      if (self.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(self.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      }
      // 如果是异步的的话. 那么它的状态会先是pending
      if (self.status === PENDING) {
        self.onFulfilledCallback.push(function() {
          setTimeout(() => {
            try {
              let x = onFulfilled(self.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
        self.onRejectedCallback.push(function() {
          setTimeout(() => {
            try {
              let x = onRejected(self.reason)
              // reject(x)
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
      }
    })
    return promise2
  }
}

Promise Api

Promise.resolve & Promise.reject

默认产生一个成功 或者 失败(拒绝)的promise

class MyPromise {
  // ...
  static resolve(data){
    //根据规范, 如果参数是Promise实例, 直接return这个实例
    if (data instanceof MyPromise) return data
    return new MyPromise(resolve => resolve(data))
  }
  
  static reject(reason){
    return new MyPromise((_, reject) => reject(reason))
  }
}

catch

catch方法其实就是执行一下then的第二个回调, 如果在then中写了第二个捕获错误的函数的话, 则不会触发catch

class MyPromise {
  // ... 
  catch(rejectFn) {
    return this.then(null, rejectFn)
  }
}

finally

finally也是返回一个promise, 无论promise是以哪种状态结束, 它都会执行执行的回调函数, 在finally之后, 还可以继续then, 并且将值原封不动传给后面的then

class MyPromise {
  // ...
  finally(callback) {
    return this.then(
      (value) => MyPromise.resolve(callback()).then(() => value),
      (reason) =>
        MyPromise.resolve(callback()).then(() => {
          throw reason
        })
    )
  }
}

all

把我们传递进来的各个promise返回结果存入一个数组, 并于所有结果完成之后返回.

用2个变量来记录, 一个记录累计结果, 一个记录执行的是第几个事件, 当累计结果的数组长度 === 执行事件的索引时, 抛出结果即可 (成功的情况下).

同时要注意, all传递进来的值并非promise, 则直接记录在 累计结果数组即可, 否则就是调用then中的resolve后记录

class MyPromise {
  // ...
  static all = function(values) {
    if (!typeof values[Symbol.iterator] === "function") {
      throw new TypeError("object is not iterable")
    }
    return new MyPromise((resolve, reject) => {
      let resultArr = []
      let currIndex = 0
      function processData(i, val) {
        resultArr[i] = val
        currIndex++
        if (currIndex === values.length) {
          resolve(resultArr)
        }
      }
      for (let i = 0; i < values.length; i++) {
        let current = values[i]
        if (current && current.then && typeof current.then === "function") {
          current.then(y => {
            processData(i, y)
          }, reject)
        } else {
          processData(i, current)
        }
      }
    })
  }
}

race

race: 竞态执行. 谁先返回谁赢, 利用promise的状态只能改变一次的特性.

简单点讲, 就是遍历执行所有的事件, 如果都是promise的话. 看谁先执行resolve方法. (resolve执行的时候, 改变了promise的状态, 那么其他后续的事件就没用了..还记得我们最开始写的那个resolve方法吗, 必须是pending状态才会执行)

class MyPromise {
  // ...
  static race = function(values) {
    if (!typeof values[Symbol.iterator] === "function") {
      throw new TypeError("object is not iterable");
    }
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < values.length; i++) {
        let current = values[i]
        if (current && current.then && typeof current.then === 'function') {
          current.then(resolve, reject)
        } else {  // 普通值直接返回
          resolve(current)
        }
      }
    })
  }
}

完整的代码

 
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

const resolvePromise = (promise2, x, resolve, reject) => {
  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise"))
  }
  if ((x !== null && typeof(x) === 'object') || typeof(x) === 'function') {
    try {
      let then = x.then
      if (typeof(then) === 'function') { 
        then.call(x, (y) => { 
          resolvePromise(promise2, y, resolve, reject) 
        }, (r) => {
          reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
       reject(e)
    }
  } else {
    resolve(x)
  }
}

class MyPromise {
  constructor(executor) {
    const self = this
    self.status = PENDING 
    self.value = undefined  
    self.reason = undefined 
    self.onFulfilledCallback = [] 
    self.onRejectedCallback = [] 

    let _resolve = (value) => {
      if (self.status === PENDING) {
        self.status = FULFILLED   
        self.value = value        
        self.onFulfilledCallback.forEach(fn => fn(self.value)) 
      }
    }

    let _reject = (reason) => {
      if (self.status === PENDING) {
        self.status = REJECTED
        self.reason = reason
        self.onRejectedCallback.forEach(fn => fn(self.reason))
      }
    }

    try {
      executor(_resolve, _reject)
    } catch (err) {
      _reject(err)
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof(onFulfilled) === 'function' ? onFulfilled : data => data 
    onRejected = typeof(onRejected) === 'function' ? onRejected : err => {throw err}
    const self = this
    const promise2 = new MyPromise(function(resolve, reject) {
      if (self.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(self.value) 
            resolvePromise(promise2, x, resolve, reject) 
          } catch (error) {
            reject(error)
          }
        }, 0)
      }
      if (self.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(self.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      }
      if (self.status === PENDING) {
        self.onFulfilledCallback.push(function() {
          setTimeout(() => {
            try {
              let x = onFulfilled(self.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
        self.onRejectedCallback.push(function() {
          setTimeout(() => {
            try {
              let x = onRejected(self.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
      }
    })
    return promise2
  }

  catch(rejectFn) {
    return this.then(null, rejectFn);
  }

  finally(callback) {
    return this.then(
      (value) => MyPromise.resolve(callback()).then(() => value),
      (reason) =>
        MyPromise.resolve(callback()).then(() => {
          throw reason
        })
    )
  }

  static resolve(data) {
    if (data instanceof MyPromise) return data

    return new MyPromise(resolve => resolve(data))
  }
  
  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason))
  }

  static all = function(values) {
    if (!typeof values[Symbol.iterator] === "function") {
      throw new TypeError("object is not iterable")
    }
    return new MyPromise((resolve, reject) => {
      let resultArr = []
      let currIndex = 0
      function processData(i, val) {
        resultArr[i] = val
        currIndex++
        if (currIndex === values.length) {
          resolve(resultArr)
        }
      }
      for (let i = 0; i < values.length; i++) {
        let current = values[i]
        if (current && current.then && typeof current.then === "function") {
          current.then(y => {
            processData(i, y)
          }, reject)
        } else {
          processData(i, current)
        }
      }
    })
  }

  static race = function(values) {
    if (!typeof values[Symbol.iterator] === "function") {
      throw new TypeError("object is not iterable");
    }
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < values.length; i++) {
        let current = values[i]
        if (current && current.then && typeof current.then === 'function') {
          current.then(resolve, reject)
        } else {
          resolve(current)
        }
      }
    })
  }
}

export default MyPromise

Referencing :

Promises/A+