手写promise核心功能其实很简单

172 阅读6分钟

前言

前面学过了如何手写call,apply,bind,理解了如何确认this的值。promise也是在日常开发经常用到的,接下来咱们可以学习一下手写promise彻底了解promise内部是怎么样的,而且也不用害怕以后面试的时候遇到手写promisepromise相关的问题了。本章节先来手写一下promise的核心功能。

构造函数

首先要实现promise要先声明一个构造函数,并且定义resolvereject

  class MyPromise {
    constructor (fun) {
      const resolve = (result) => {
        console.log('resolve---', result);
      }
      const reject = (result) => {
        console.log('reject---', result);
      }
      fun(resolve, reject)
    }
  }
  new MyPromise((resolve, reject) => {
    // resolve('success')
    resolve('error')
  })

上方写好了我们promise的一个基础的构造函数,主要为new创造promise的时候呢接收一个函数,函数内有两个参数,参数也为函数,可以接收到值。所有咱们new promise的时候呢接收到函数,然后执行通过参数的方式再把resolvereject传出去。

状态及原因

上面是已经可以调用resolvereject,接下来我们就要对promise添加原因并且使其状态不可以逆,进一步还原promise

  const PENDING = 'pending'
  const FULFILLED = 'fulfilled'
  const REJECTED = 'rejected'
  class MyPromise {
    constructor (fun) {
      this.state = PENDING
      this.result = undefined
      const resolve = (result) => {
        if (this.state === PENDING) {
          this.state = FULFILLED
          this.result = result
        }
      }
      const reject = (result) => {
        if (this.state === PENDING) {
          this.state = REJECTED
          this.result = result
        }
      }
      fun(resolve, reject)
    }
  }
  const P = new MyPromise((resolve, reject) => {
    // resolve('success')
    reject('error')
  })
  console.log(P.state);
  console.log(P.result);

上面我们首先声明了三个变量分别对于promise的三个状态,初始状态为pending,当调用成功或者失败的函数后先判断是否为默认状态再设置对应的状态,实现状态不可逆,并且接收当前的状态和原因。

失败成功回调

下面我们实现then的方法,then可以传入两个回调函数,一个成功的回调和一个失败的回调他们会在调用resolvereject的时候触发,且会值传入到对应的函数内执行

    then (onFulfilled, onRejected) {
      onFulfilled = typeof onFulfilled === "function" ? onFulfilled : x => x
      onRejected = typeof onRejected === "function" ? onRejected : x => { throw x }
      if (this.state === FULFILLED) {
        onFulfilled(this.result)
      } else if (this.state === REJECTED) {
        onRejected(this.result)
      }

then方法呢接收两个函数参数,分别对应了成功和失败,来根据当前的状态执行对应的函数并且把resolvereject传入的值再传回给函数。当然我们也需要判断then传入的是否为函数,如果不是函数替换为一个恒等函数,reject则是直接抛出错误

异步调用

上面呢实现了promisethen方法的成功和失败的回调,但是现在都是同步的,下面咱们要实现异步情况下的调用

  class MyPromise {
    #handlers = []
    constructor (fun) {
      ...
      const resolve = (result) => {
        if (this.state === PENDING) {
          ...
          this.#handlers.forEach(({ onFulfilled }) => {
            onFulfilled(this.result)
          })
        }
      }
      const reject = (result) => {
        if (this.state === PENDING) {
            ...
          this.#handlers.forEach(({ onRejected }) => {
            onRejected(this.result)
          })
        }
      }
      fun(resolve, reject)
    }
    then (onFulfilled, onRejected) {
    ...
     else if (this.state === PENDING) {
        this.#handlers.push({onFulfilled, onRejected})
      }
    }
  }

首先咱们在MyPromise声明了 一个私有属性来放所有调用then方法时状态为默认状态的时候传入的函数,等到执行resolvereject拿出来所有存入的函数遍历执行,这样就实现了异步的执行。也就是把再pending状态下传入then内的函数存了起来,等到执行失败或成功函数的时候再拿出来进行执行

异步任务

promise呢是异步进行执行的,但是现在写的方法并没有进行异步的执行,例如

  const P = new MyPromise((resolve, reject) => {
    resolve('success')
  })
  console.log(11);
  P.then(res => {
    console.log(res);
  })
  console.log(22);

正常promise执行的话打印顺序为11->22->success,但是我们写的没有考虑异步的情况,直接执行的话结果就会为11->success->22,下面我们封装一个函数来实现异步的执行

runAsynctask (callback) {
    if (typeof queueMicrotask === "function") {
      queueMicrotask(callback);
    } else if (typeof MutationObserver === "function") {
      const obs = new MutationObserver(callback)
      const divNode = document.createElement("div");
      obs.observe(divNode, { childList: true })
      divNode.innerText = 'onlooker'
    } else {
      setTimeout(callback, 0)
    }
  }

上面主要是通过queueMicrotask(直接异步执行任务),MutationObserver(观察元素执行异步任务),setTimeout,来实现任务的异步执行,下面只需要再执行的时候使用runAsynctask传入要执行的任务就可以完成异步的执行

   then (onFulfilled, onRejected) {
      ...
      if (this.state === FULFILLED) {
        runAsynctask(() => {
          onFulfilled(this.result)
        })
      } else if (this.state === REJECTED) {
        runAsynctask(() => {
          onRejected(this.result)
        })
      } else if (this.state === PENDING) {
        this.#handlers.push({
          onFulfilled: () => {
            runAsynctask(() => {
              onFulfilled(this.result)
            })
          }, onRejected: () => {
            runAsynctask(() => {
              onRejected(this.result)
            })
          }
        })
      }
    }

这个我们在进行最开始的执行的话,顺序就变为了11->22->success,也就实现了任务的异步执行

链式调用

下一步我们实现一下then得链式调用,也是我们经常用到得。实现链式调用呢其实也就是在then内再返回一个promise就可以了

处理异常和普通内容

处理异常和内容得话也就是我第一层得pormisethen内返回出去了一个值,要在下一层进行接受,这样得话也就需要在then内返回得promise执行对于得resolvereject具体实现如下

           //返回新Promise实例
          const p2 = new HMPromise((resolve, reject) => {
            if (this.state === FULFILLED) {
              runAsynctask(() => {
                //  获取返回值
                try {
                  const x = onFulfilled(this.result);
                  //处理返回值
                  resolve(x);
                } catch (error) {
                  // 处理异常
                  // console.log('捕获异常:', error)
                  reject(error);
                }
              });
            } 
            ......
          });

          return p2;

上面代码逻辑为,onFulfilled也就是then传入得函数,这个如果有进行return也会可以拿到返回得值,那么直接resolve(x)要返出去得promise,那么下面得then就可以接受到上面传入得值了,如果是进行了错误抛出,这边也可以捕获到错误,然后传入给reject,下一层then也可以拿到这个错误

处理返回promise

上面实现了普通内容和异常得链式调用接受结果只,但是如果在then内返回一个promise

    const P = new MyPromise((resolve, reject) => {
        resolve("success");
      });
      P.then((res) => {
        // console.log(res);
        return new MyPromise((resolve, reject) => {
            resolve("success22");
        })
      }).then(
        (res) => {
          console.log(res);
        },
        (e) => {
          console.log(e);
        }
      );

正常情况下我们是需要在下面接受到success22得,但是因为现在没有做处理,那么下面现在是直接接受到了一个promise,我们需要在处理链式调用得时候判断他是否为promise得实例,如果是得话需要拿到resolve或是reject内得值,然后调用要返回得promise得方法调用对应得函数

     if (this.state === FULFILLED) {
              runAsynctask(() => {
                try {
                  const x = onFulfilled(this.result);
                  if(x instanceof MyPromise){
                    x.then(res=>{resolve(res)},err=>{reject(err)});
                  }else{
                    resolve(x);
                  }
                } catch (e) {
                  reject(e);
                }
              });
            } 

首先判断是否属于MyPromise得实例,如果是得话则执行拿到结果值传出。

处理重复调用

接下来我们处理一下porimse重复调用得问题,也就是需要判断then里面得返回值与then得返回值不可以一致,如果一致得话则直接抛出一个错误


const x = onFulfilled(this.result);
   if (x === ms) {
     throw new TypeError("Chaining cycle detected for promise #<Promise>");
    }

rejected状态

上面得处理返回值,返回promise。重复调用都是在fulfilled内得处理,接下来需要把这些逻辑在搬入到rejected内,可以把那一段代码封装为一个函数,传入参数直接调用。

      function resolvePromise(ms, x, resolve, reject) {
        if (x === ms) {
          throw new TypeError("Chaining cycle detected for promise #<Promise>");
        }
        if (x instanceof MyPromise) {
          x.then(
            (res) => {
              resolve(res);
            },
            (err) => {
              reject(err);
            }
          );
        } else {
          resolve(x);
        }
      }

pending状态和这两个是一样的,函数封装好了直接调用传入参数便可以了

结尾

以上呢就已经实现了promise得核心功能,了解了promise得核心功能内部运行机制对于后面得日常开发上promise得使用会更加灵活。当然promise还有实例方法和多个静态方法,我们会在下一章节做详细得讲解,彻底读懂promise,做到不管是日常开发还是面试都可以轻松应对