手撕Promise:Promise.then详解


theme: healer-readable

本文正在参加「金石计划 . 瓜分6万现金大奖

前几天我们在手撕Promise 源码篇的时候,对于Promise.then 的实现只是给出了带有注解的源代码,那么让我们一步一步揭开.then的神秘面纱,如何实现.then的链式调用呢?

官方的Promise.then

let p1 = new Promise((resolve, reject) => {
  resolve(123); 
})
// Promise {<fulfilled>: 123}
p1.then(
    res => console.log(res,'success'),
    err => console.log(err,'failed')
)
// 123 'success'
// Promise {<fulfilled>: undefined}

Promise.then的链式调用

let p1 = new Promise((resolve, reject) => {
  resolve(123); 
})
.then(res => res * 3);
.then(res => res * 3);
.then(res => {
  console.log(res);  // 1107
})

从以上两个例子我们可以看到,Promise.then方法接收两个回调函数作为参数,第一个参数是fulfilled状态的回调函数,第二个参数是rejected状态的回调函数,当promise的状态为'fulfilled'会执行第一个回调函数,当状态为'rejected'时执行第二个回调函数。

这并不意味着'pending'状态的Promise对象不能在后面接.then,而是当Promsie的状态变更为'fulfilled'或者'rejected'状态的时候,才会触发里面两个回调函数其中一个,并且.then支持链式调用,链式传参,所以上一次的.then的结果会影响到下一个.then。

知道以上这些我们就可以试试来自己实现Promsie.then,在看我如何手撕Promsie这篇文章中我们已经手把手教大家实现了resolvereject两个方法,这里我们就直接接着前一篇文章的进度,不明白这两个方法怎么实现的朋友可以去看看之前的文章。

class myPromise {
  constructor(executor) {
    this.status = 'pending'; // 变更promise的状态
    this.value = null;
    try { 
    executor(this.resolve.bind(this), this.reject.bind(this)); // new 一个myPromise 得到的实例对象里面有两个函数
    } catch (error) { 
    this.reject(error) 
    } 
  }

  resolve(value) {
    if (this.status !== 'pending') return;
    this.status = 'fulfilled'; // 变更promise的状态
    this.value = value;
  }

  reject(reason) {
    if (this.status !== 'pending') return
    this.status = 'rejected';
    this.value = reason;
  }
}

首先.then返回的也是一个Promise对象,所以我们第一步

then(onFulfilled, onRejected) {

    return new myPromise((resolve, reject) => {
        
  }

第二步.then的执行虽然不受我们控制,但是其内部的回调的执行取决于'Promise的状态',只要Promise的状态处于 'pending' 状态,就算后面接了再多的.then都不会执行,所以我们要将成功或者失败的回调都使用两个队列存起来,等到Promise的状态变更的时候再根据队列先进先出的特性,依次将队列中的回调执行。

我们在构造器函数中定义了两个队列 (只使用push和shift方法,即可人为认定队列,或者使用pop和unshift方法,原理相同) onFulfilledCallbacksonRejectedCallbacks,这样我们在调用resolve或者reject方法的时候,只需要判断,相应的回调函数队列中是否有未执行的函数,使用shift方法切出来之后执行。

  constructor(executor) {
    this.status = 'pending'; 
    this.value = null;
    this.onFulfilledCallbacks = []; // 用来保存成功的回调(处理异步)
    this.onRejectedCallbacks = []; // 用来保存失败的回调(处理异步)
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error)
    }
  }

  resolve(value) {
    if (this.status !== 'pending') return;
    this.status = 'fulfilled';
    this.value = value;
    // 调用then里面的回调
    while (this.onFulfilledCallbacks.length) {  // 判断是否存在未执行的回调函数
      this.onFulfilledCallbacks.shift()(this.value)  // 存在则切出来执行掉
    }
  }

  reject(reason) {
    if (this.status !== 'pending') return
    this.status = 'rejected';
    this.value = reason;
    while (this.onRejectedCallbacks.length) {  // 判断是否存在未执行的回调函数
      this.onRejectedCallbacks.shift()(this.value)  // 存在则切出来执行掉
    }
  }

第三步,在.then中我们需要判断Promise的状态,当状态为'fulfilled'则调用resolve方法,状态为'rejected'则调用'reject'方法,如果状态为'pending'那就将回调存到对应的回调队列中去。

因为不能将Promise对象本身作为回调参数,所以我们将要返回的Promise对象声明一个变量,以便于判断是否将自身作为回调传进来,如果将自身作为回调我们就是抛出一个错误,如果参数就是一个新的Promise对象,则直接调用.then

then(onFulfilled, onRejected) {
   var thenPromise = new myPromise((resolve, reject) => {
      const resolvePromise = callback => {
          try {
            const x = callback(this.value);  // 声明回调的调用
            if (x === thenPromise) {  // 你正在返回自身
              throw new Error('不允许返回自身!');
            }
            if (x instanceof myPromise) { // 返回的是一个Promise对象
              x.then(resolve, reject);
            } else { // 直接返回一个值,作为resolve的值,传递给下一个.then
              resolve(x);
            }
          } catch (error) {
            reject(error);
            throw new Error(error);
          }
        })
      }

      if (this.status === 'fulfilled') {
        resolvePromise(onFulfilled)
      } else if (this.status === 'rejected') {
        resolvePromise(onRejected)
      } else if (this.status === 'pending') {
        this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled));  // 考虑this的指向问题,直接使用bind方法显示绑定
        this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected));  // 考虑this的指向问题,直接使用bind方法显示绑定
      }
    })
    return thenPromise
  }

最后一步,我们要知道Promise.then是属于异步代码中的微任务事件,其执行顺序会在宏任务之后执行,但是我们目前实现的效果是.then这一步的逻辑还是同步代码,所以我们在返回的Promise对象的外面套一层setTimeout,这样返回出去的.then的逻辑,会去到下一次的宏任务队列,这样就实现了.then的执行会比同步代码稍晚一些,但是官方并不是使用setTimeout实现的。

完整版then方法代码:

class myPromise {
  constructor(executor) {
    this.status = 'pending'; 
    this.value = null;
    this.onFulfilledCallbacks = []; // 用来保存成功的回调(处理异步)
    this.onRejectedCallbacks = []; // 用来保存失败的回调(处理异步)
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error)
    }
  }

  resolve(value) {
    if (this.status !== 'pending') return;
    this.status = 'fulfilled';
    this.value = value;
    // 调用then里面的回调
    while (this.onFulfilledCallbacks.length) {
      this.onFulfilledCallbacks.shift()(this.value)
    }
  }

  reject(reason) {
    if (this.status !== 'pending') return
    this.status = 'rejected';
    this.value = reason;
    while (this.onRejectedCallbacks.length) {
      this.onRejectedCallbacks.shift()(this.value)
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
    onRejected = typeof onRejected === 'function' ? onRejected : val => { throw val }

    var thenPromise = new myPromise((resolve, reject) => {

      const resolvePromise = callback => {
        setTimeout(() => {   // 让整个回调函数比同步代码晚一点执行,官方不是使用setTimeout实现
          try {
            const x = callback(this.value);
            if (x === thenPromise) {  // 你正在返回自身
              throw new Error('不允许返回自身!');
            }
            if (x instanceof myPromise) { // 返回的是一个Promise对象
              x.then(resolve, reject);
            } else { // 直接返回一个值,作为resolve的值,传递给下一个.then
              resolve(x);
            }
          } catch (error) {
            reject(error);
            throw new Error(error);
          }
        })
      }

      if (this.status === 'fulfilled') {
        resolvePromise(onFulfilled)
      } else if (this.status === 'rejected') {
        resolvePromise(onRejected)
      } else if (this.status === 'pending') {
        this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled));
        this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected));
      }
    })
    return thenPromise
  }
}

让我们看看最终的效果:

image.png

希望这篇文章能够帮助到大家去更好的理解.then的实现原理,有任何不足的之处可以在评论区留言,大家共同进步!

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容