promise实现以及最详细的注解(完美符合A+规范)

269 阅读11分钟

promise实现以及最详细的注解(完美符合A+规范)

promise是目前前端解决异步的最好方式,解决了回调地狱的痛点,开发中常用的async ,await 也是promise的语法糖。而真正想要搞明白promise,最好的方式是实现一遍promise,本文会讲解promise每一处实现。

promise我自己实现了也有4-5次了,一开始就是抄,到了后面会自己思考哪一步是否多余,到最后发现一开始抄的其实已经是精华了,但是新人一下子看promise实现,将近200行一定会产生畏惧,而我也从头开始写一个promise,来帮助大家理解,顺便加强自己的记忆。

1. promise简介

  1. “promise” is an object or function with a then method whose behavior conforms to this specification.

  2. “thenable” is an object or function that defines a then method.

  3. “value” is any legal JavaScript value (including undefined, a thenable, or a promise).

  4. “exception” is a value that is thrown using the throw statement.

  5. “reason” is a value that indicates why a promise was rejected.

    这是A+规范上的promise定义,我来总结一下:

    promise是一个对象或者方法,有一个then方法挂载在上面。

    promise有一个value。

    promise有一个reason,说明为什么rejected


2. promise实现 part1

​ 在A+规范上,promise实现它分为了2.1,2.2,2.3,三个part,大致上可以实现3个函数去实现3个part。但是在具体实现里每个函数里的功能可能会发生重叠。

​ 我会在代码里备注每一处规范,并且在代码后进行总结。

const PENDING = 'pending'
const FULFILLED = 'fulfuilled'
const REJECTED = 'rejected'

function Promise(executor) {
  // 这里必须要将this,给保存下来。
  const self = this;
  // 每一个promise初始状态为pending
  self.status = PENDING;
  // 这里是记录回调函数的方法。用处之后会说
  self.fulfiledCallbacks = [];
  self.rejectedCallbacks = [];
  // 定义resolve方法, resolve方法接受一个参数,会将promise的value置为这个参数。
  function resolve(value) {
    // 确保只有当PENDING时才会执行resolve
    if (self.status === PENDING) {
      // 修改此时的promise status 确保只会执行一次resolve
      self.status = FULFILLED
      self.value = value
      // 执行then方法的函数
      self.fulfiledCallbacks.forEach(fn => fn())
    }
  }
  // 定义reject方法, reject方法接受一个参数,会将promise的reason置为这个参数。
  function reject(reason) {
    if (self.status === PENDING) {
      self.status = REJECTED
      self.reason = reason
      self.rejectedCallbacks.forEach(fn => fn())
    }
  }
  
  try {
    // 当执行executor 这个传入的方法时,如果报了异常,需要捕捉到并且reject出来。
    executor(resolve,reject)
  } catch(e) {
    reject(e)
  }
}

上面的代码虽然每一行都有注释,但是第一次阅读promise相关代码的话一定会有疑问。

  1. const self = this; 为什么要记录保存
  2. fulfiledCallbacksrejectedCallbacks是干什么的?

逐一分析一下:

  1. 这里的this是什么呢?

    举一个例子 const promise = new Promise()的话,那么this指向的就是promise这个被我们new出来的对象,然而我们是知道promise是用来解决异步的,在异步执行时,this指的就不是这个对象了。

    最典型的例子:

    function test() {
      this.a = 1
    }
    test.prototype.func1 = function() {
      setTimeout(this.func2)
    }
    test.prototype.func2 = function() {
      console.log(this.a);
    }
    
    const test1 = new test();
    test1.func1(); // undefined
    test1.func2(); // 1
    

    setTimeout中的方法,this会指向window,这就是为什么要一开始储存this的定义的原因了,因为this会变,而储存成变量后则不会变。

  2. 这里用到的一个思想是函数队列。

    当出现下面这种情况:

    const promise = new Promise()
    promise.then(() => {})
    promise.then(() => {})
    promise.then(() => {})
    

    一个promise如果被多次调用then方法,如果一开始promise的状态是pending,那我们就应该把then里面的函数给储存下来,当promise的状态改变后,执行对应的函数。

    这是符合A+规范中的2.2.6。

    2.2.6 then may be called multiple times on the same promise.

    1. If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.
    2. If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.

这样的话,我们是把part1给写完了。

3. promise实现 part2

// then方法要接受2个参数,分别是两个方法,当promise是成功或者失败时调用的方法
Promise.prototype.then = function(onFulfilled, onRejected) {
  // 同样要保存this
  const self = this;
  // 判断onfulfilled是否是函数,如果是函数,那就不用修改,否则的话,按照2.2.7.3的规则,给什么就返回什么,但是仍然是个函数。
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  // 判断onRejected是否是函数,如果是函数,那就不用修改。
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
  // 无论如何then方法都会返回一个promise,也就是返回这个promise2,
  const promise2 = new Promise((resolve, reject) => {
    // 当promise的当前状态是完成时
    if (self.status === FULFILLED) {
      // 我们用setTimeout来模拟then方法,执行onFulfilled方法或者执行onRejected方法都是异步的过程。
      setTimeout(() => {
        // 这里一定要用try来捕捉异常,如果当在执行onFulfilled or onRejected,出现异常时规范中要求promise2 reject异常。
        try {
          // 这里会执行onFulfilled方法,接受promise的value作为参数,至于为什么储存下来以及执行resolvePromise方法会在之后说。
          let x = onFulfilled(self.value)
          resolvePromise(promise2,x, resolve, reject);
        } catch(e) {
          reject(e)
        }
      }) 
    }
    if (self.status === REJECTED) {
      setTimeout(() => {
        try {
          let x = onRejected(self.reason);
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) {
          reject(e)
        }
      })
    }

    if (self.status === PENDING) {
      // 如果promise的状态是pending,就应该按照之前所说将其储存到回调队列里。
      self.fulfiledCallbacks.push(
        // 需要注意,这里需要用函数包裹起来setTimeout方法,因为我们放进去的是一个函数,之后当状态改变调用这个函数也就是setTimeout异步函数。
        () => {
          setTimeout(() => {
            try {
              let x = onFulfilled(self.value)
              resolvePromise(promise2,x, resolve, reject);
            } catch(e) {
              reject(e)
            }
          }) 
        }
      )
      // 逻辑同上
      self.rejectedCallbacks.push(
        () => {
          setTimeout(() => {
            try {
              let x = onRejected(self.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) {
              reject(e)
            }
          })
        }
      )
    }

  })

  // 返回这个对象
  return promise2;
}


then方法的疑问点应该主要在于resolvePromise这个function,这个function其实是在part3中实现的。

这个方法出现的目的其实就是为了根据规范去确定promise2,也就是promise.then()究竟返回什么样的一个promise2,这个promise2的状态究竟是什么样的。

4. promise实现 part3

// 接受一个promise,x,x为执行onFulfilled or onRejected方法的值,同时为了实现方便,传入promise的resolve与reject
function resolvePromise(promise2, x, resolve, reject) {
  // 如果promise2 与 x 指的是一个对象,也就是说onFulfilled or onRejected返回的是promise2,那显然会出现一个闭环的情况,无限递归,因此需要抛出异常。
  if (promise2 === x) {
    throw TypeError('chain error');
  }
  // 这里就是判断x是否是object或者是function 
  if (x && typeof x === 'object' || typeof x === 'function') {
    // 声明一个变量 used,确保执行一次后就更改used,之后如果再次执行,used为true直接跳过。
    let used = false;
    // 这里必须得用try来捕捉异常,无论是取x.then的异常还是calling then的异常
    try {
      // 先将then取出来,这时候需要捕捉异常
      let then = x.then;
      // 如果then是一个方法
      if (typeof then === 'function') {
        // 完全遵循2.3.3.3的规则 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise
        then.call(x, (y) => {
          // 注意在这里,如果used,也就是说已经执行过then.call ,那么就会忽视这段代码
          if (used) {
            return ;
          }
          used = true;
          resolvePromise(promise2, y, resolve, reject);
        }, (r) => {
          if (used) {
            return ;
          }
          used = true;
          reject(r);
        })
      }
      // 如果then不是一个方法,那么直接resolve(x) 
      else {
        resolve(x);
      }
    } catch(e) {
      // 这里同样也需要判断,这点容易忽视, 因为这里不只是判断x.then抛出的异常,也有可能是在执行then.call的时候出现的异常,如果已经执行过resolvePromise or rejectPromise, 就应该忽视下面的
      if (used) {
        return ;
      }
      used = true;
      reject(e);
    }
  } else {
    // 如果都不是,那就是一个基础类型,那么用resolve将其包裹起来返回
    resolve(x);
  }

}

part3主要就是解决链式调用中 promise2的状态。

如果用心的copy代码逐行分析,问题应该不会很大了,我来举一个需要用到used的地方,相信大家就能彻底明白这个函数为什么这样实现了。


const promise = new Promise((resolve,reject) => {
  resolve(1);
})

const promise2 = promise.then((value) => {
  // 创建一个函数
  const fun = function() {
    
  }
  // 这个函数上有then方法,传递进去的两个参数是resolve和reject。
  fun.prototype.then = function fun1(resolve, reject){
    resolve(1)
    reject(2);
  }
  // 返回这个构造函数创建的对象
  const obj = new fun();
  return obj;
})

setTimeout(() => {
  console.log(promise2); // Promise { 1 }, 是会忽视reject(2)的 如果调换顺序就会打印出reject 2.
})

这里一定要理解在实现当中,我们用的then.call的形式调用这个方法,是传递进去两个函数,这两个函数分别有一个y参数和r参数。而在定义的then方法中resolve,和reject就是两个方法,会在我们的实现里以then.call的形式调用,在定义的then方法中,调用resolvereject方法就是对应的在实现中的2个参数,请务必搞清楚这其中的关系。(我也是实现了多次后才搞明白,js的函数参数非常的绕)

虽然上面的resolvePromise看上去非常复杂,但是实际上如果把used的相关逻辑去掉,其他的代码几乎都是按照规范上的规则进行书写的。**(没有自己的想法,无情的码字机器)**之后可以继续深究为什么规范是这样定义的,有什么好处。

5. 总结

promise的实现已经完成了,我的实现也是参考了许多人的版本,最后实现了多次,并且几乎每一行代码都带有自己的理解。

贴一下所有代码,而promise自带的许多方法,在之后会实现。

const PENDING = 'pending'
const FULFILLED = 'fulfuilled'
const REJECTED = 'rejected'

function Promise(executor) {
  // 这里必须要将this,给保存下来。
  const self = this;
  // 每一个promise初始状态为pending
  self.status = PENDING;
  // 这里是记录回调函数的方法。用处之后会说
  self.fulfiledCallbacks = [];
  self.rejectedCallbacks = [];
  // 定义resolve方法, resolve方法接受一个参数,会将promise的value置为这个参数。
  function resolve(value) {
    // 确保只有当PENDING时才会执行resolve
    if (self.status === PENDING) {
      // 修改此时的promise status 确保只会执行一次resolve
      self.status = FULFILLED
      self.value = value
      // 执行then方法的函数
      self.fulfiledCallbacks.forEach(fn => fn())
    }
  }
  // 定义reject方法, reject方法接受一个参数,会将promise的reason置为这个参数。
  function reject(reason) {
    if (self.status === PENDING) {
      self.status = REJECTED
      self.reason = reason
      self.rejectedCallbacks.forEach(fn => fn())
    }
  }
  
  try {
    // 当执行executor 这个传入的方法时,如果报了异常,需要捕捉到并且reject出来。
    executor(resolve,reject)
  } catch(e) {
    reject(e)
  }
}

// then方法要接受2个参数,分别是两个方法,当promise是成功或者失败时调用的方法
Promise.prototype.then = function(onFulfilled, onRejected) {
  // 同样要保存this
  const self = this;
  // 判断onfulfilled是否是函数,如果是函数,那就不用修改,否则的话,按照2.2.7.3的规则,给什么就返回什么,但是仍然是个函数。
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  // 判断onRejected是否是函数,如果是函数,那就不用修改。
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
  // 无论如何then方法都会返回一个promise,也就是返回这个promise2,
  const promise2 = new Promise((resolve, reject) => {
    // 当promise的当前状态是完成时
    if (self.status === FULFILLED) {
      // 我们用setTimeout来模拟then方法,执行onFulfilled方法或者执行onRejected方法都是异步的过程。
      setTimeout(() => {
        // 这里一定要用try来捕捉异常,如果当在执行onFulfilled or onRejected,出现异常时规范中要求promise2 reject异常。
        try {
          // 这里会执行onFulfilled方法,接受promise的value作为参数,至于为什么储存下来以及执行resolvePromise方法会在之后说。
          let x = onFulfilled(self.value)
          resolvePromise(promise2,x, resolve, reject);
        } catch(e) {
          reject(e)
        }
      }) 
    }
    if (self.status === REJECTED) {
      setTimeout(() => {
        try {
          let x = onRejected(self.reason);
          resolvePromise(promise2, x, resolve, reject);
        } catch(e) {
          reject(e)
        }
      })
    }

    if (self.status === PENDING) {
      // 如果promise的状态是pending,就应该按照之前所说将其储存到回调队列里。
      self.fulfiledCallbacks.push(
        // 需要注意,这里需要用函数包裹起来setTimeout方法,因为我们放进去的是一个函数,之后当状态改变调用这个函数也就是setTimeout异步函数。
        () => {
          setTimeout(() => {
            try {
              let x = onFulfilled(self.value)
              resolvePromise(promise2,x, resolve, reject);
            } catch(e) {
              reject(e)
            }
          }) 
        }
      )
      // 逻辑同上
      self.rejectedCallbacks.push(
        () => {
          setTimeout(() => {
            try {
              let x = onRejected(self.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) {
              reject(e)
            }
          })
        }
      )
    }
  })
  // 返回这个对象
  return promise2;
}

// 接受一个promise,x,x为执行onFulfilled or onRejected方法的值,同时为了实现方便,传入promise的resolve与reject
function resolvePromise(promise2, x, resolve, reject) {
  // 如果promise2 与 x 指的是一个对象,也就是说onFulfilled or onRejected返回的是promise2,那显然会出现一个闭环的情况,无限递归,因此需要抛出异常。
  if (promise2 === x) {
    throw TypeError('chain error');
  }
  // 这里就是判断x是否是object或者是function
  if (x && typeof x === 'object' || typeof x === 'function') {
    // 声明一个变量 used,确保执行一次后就更改used,之后如果再次执行,used为true直接跳过。
    let used = false;
    // 这里必须得用try来捕捉异常,无论是取x.then的异常还是calling then的异常
    try {
      // 先将then取出来,这时候需要捕捉异常
      let then = x.then;
      // 如果then是一个方法
      if (typeof then === 'function') {
        // 完全遵循2.3.3.3的规则 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise
        then.call(x, (y) => {
          // 注意在这里,如果used,也就是说已经执行过then.call ,那么就会忽视这段代码
          if (used) {
            return ;
          }
          used = true;
          resolvePromise(promise2, y, resolve, reject);
        }, (r) => {
          if (used) {
            return ;
          }
          used = true;
          reject(r);
        })
      }
      // 如果then不是一个方法,那么直接resolve(x) 
      else {
        resolve(x);
      }
    } catch(e) {
      // 这里同样也需要判断,这点容易忽视, 因为这里不只是判断x.then抛出的异常,也有可能是在执行then.call的时候出现的异常,如果已经执行过resolvePromise or rejectPromise, 就应该忽视下面的
      if (used) {
        return ;
      }
      used = true;
      reject(e);
    }
  } else {
    // 如果都不是,那就是一个基础类型,那么用resolve将其包裹起来返回
    resolve(x);
  }

}

Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected)
}

Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

module.exports = Promise

可以用promises-aplus-tests去测试你实现的promise是否完全符合A+规范,具体方法可以github看一下这个库。