[聊一聊]Promise以及Promise.then的实现原理

3,128 阅读5分钟

大家好我是王大傻,最近呢,在工(mo)作(yu)群闲聊之际,发现有些朋友对于Promise的实现以及then方法的实现并不是很清晰,所以想借着这次机会,刚好手写实现下Promise

image.png 那么在正式学习前呢,咱们先来一起唠唠EvenLoop事件轮询机制。(图中所使用的分析网站地址已经放在文章最后了)

分析

首先,如图所示,我们执行事件中,无非于两种

  1. 同步任务
  2. 异步任务 同步任务自然不用多说,按照顺序,当前调用栈依次执行,如:
function foo1(){
  console.log(2)
}
new Promise((res,rej)=>{
    setTimeout(()=>{
        res(1)
    },3000)
}).then(res=>{
    console.log(res)
})
foo1()

在上述函数中,我们首先执行的就是Promise(Promise实例阶段就是同步任务),所以也就有了Promise.resolve为同步任务自此,我们先执行了Promise,但是,此时Promise的内部出现了一个定时器,怎么办呢。。我们想想想,定时器是一个宏任务,所以它暂时会被推入宏任务队列,代码继续向下执行,由于没有更改Promise的状态所以此时的then还未执行,只是将其挂入到Promise内部的调用队列里面。代码自然来到了foo1部分,控制台打印2,至此,我们同步任务已经完成了,然后开始异步任务,异步任务执行顺序微任务->宏任务,我们先来到微任务队列,并没有执行的所以开始执行宏任务中的定时器,此时我们resolve了1,并且将微任务Promise.then推入了微任务队列,此时进行检查后,我们执行微任务,打印出来1,至此,任务一轮走完了,那么假如我们这样修改呢

function foo1(){
  console.log(2)
}
new Promise((res,rej)=>{
    setTimeout(()=>{
        res(1)
    },3000)
}).then(res=>{
setTimeout(()=>{
 console.log(res)
    return res
},100)
}).then(res=>{
console.log(res)
})
foo1()

此时的结果又是什么呢?211? 嗯,显然不是,代码是考不上211的,开句玩笑

image.png 那么正确结果是什么呢?这里就不卖关子了,大家听我分析

image.png 首先我们第一次输出2,这个是没问题的,毕竟是同步任务,但是在挂起Promise里面第一个定时器后,我们代码执行还是依次,此时先回去挂起第一个微任务但是发现里面有定时器,所以重复上述逻辑,将定时器推入到宏任务,接着将下一个微任务也去挂起,然后执行完我们第一个定时器后返回了值为1,那么此时第一个then函数和第二个then函数究竟先调用哪个呢,因为我们第一个里面有定时器,所以定义的是第二个then,而又由于then取得的res值是由前面的函数传递过来的,此时第一个then函数并没有传递任何值,所以打印undefined,最后执行我们的第一个then函数,输出1,所以答案是 2 undefined 1 image.png

简单了解了上述原理后,我们来实现下Promise吧 话不多说先来分析一波

分析

  1. Promise三种状态 reject pending resolve
  2. 值的储存 value
  3. 成功失败函数的储存
  4. 静态方法以及内部方法
  5. 返回的实例 包含两个参数 是两个函数 那么根据上述我们先实现下Promise
const PENDING = 'pending'
const RESOLVE = 'resolve'
const REJECT = 'reject'

class MyPromise {
   // 先做初始化
   status = PENDING // 初始化状态
   value = undefined // 定义初始值
   onSuccessQueue = [] // 成功函数任务队列
   onFailedQueue = [] // 失败函数任务队列
   constructor(exe) {
      const resolve = (value) => {
         const doFn = () => {
            // 确保有且只有一次状态改变
            if (this.status !== PENDING) return
            this.value = value
            this.status = RESOLVE
            while (this.onSuccessQueue.length) {
                this.onSuccessQueue.shift()()
            }
         }
         // 模拟异步
         setTimeout(doFn, 0)
      }
      const reject = (value) => {
         const doFn = () => {
            // 确保有且只有一次状态改变
            if (this.status !== PENDING) return
            this.value = value
            this.status = REJECT
            while (this.onFailedQueue.length) {
                this.onFailedQueue.shift()()
            }
         }
         // 模拟异步
         setTimeout(doFn, 0)
      }
      try {
         exe(resolve, reject)
      } catch (e) {
         reject(e)
      }
   }
 }

我们在实现过程中通过定时器去模拟了异步的操作,但其实异步并不是微任务,所以和真正的Promise比起来还是有差距的,在此我们仅去了解原理即可

实现完了Promise,那我们趁热打铁继续来实现下Promise.then吧 image.png

Promise.then实现分析

  1. then函数中也是返回两个方法
  2. 返回值是个新的promise实例
  3. 创建成功和失败函数
  4. 函数中判断是否等于当前返回函数 容易导致回调地狱
  5. 判断是否继承于父级 说明是个promise 调用then方法 并传入当前的处理函数
  6. 判断状态并初始化函数 该执行执行 该存储存储 话不多说上代码
then(onFulfilled , onFailed ) {
   // 定义成功函数 判断传入类型
   onFulfilled =
      typeof onFulfilled == "function" ? onFulfilled : (v) => v;
   // 失败函数处理 忽略函数之外的其他值 抛出异常
   onFailed =
      typeof onFailed == "function"
         ? onFailed
         : (reason) => {
            throw reason;
            return
         };
   const nowPromise = new MyPromise((resolve, reject) => {
      // 定义统一处理函数
      const execFun = (fn, val) => {
         try {
            let res = fn(val);
            // 避免回调地狱
            if (nowPromise === res) {
               reject(new TypeError("Chaining cycle detected for promise #<MyPromise>"));
               return;
            }
            // promise结果 所以直接调用then
            if (res instanceof MyPromise) {
               res.then(resolve, reject);
            } else {
               // 非promise结果
               resolve(res);
            }
         } catch (e) {
            reject(e);
         }
      };
      // 成功函数调用
      const success = () => {
         execFun(onFulfilled,this.value)
      }
      // 失败函数调用
      const failed = () => {
         execFun(onFailed,this.value)
      }
      if (this.status === PENDING) {
         this.onSuccessQueue.push(success)
         this.onFailedQueue.push(failed)
      } else if (this.status === RESOLVE) {
         success()
      } else {
         failed()
      }
   })
   return nowPromise
}

至此为止,我们的Promise也实现完成了,让我们试一试刚才所讲的案例吧

image.png 然后,就求一波点赞和关注吧。。。。。。

测试事件执行顺序网站地址

image.png