带你手写一下Promise吧!

前言:本文章默认你已会promise的基本使用

首先我们给每个Promise实例都维护三个字段,分别是:

  • 状态(初始化为pending)
  • 数据(初始化为undefined)
  • 执行队列(初始化为[])

我们就将这三个字段分别取名为state,value,queue吧

那么我们就可以在Promise地构造函数中写下这么几句代码

class Promise {
    constructor () {
        this._state = 'pending'  // 状态
        this._value = undefined  // 数据
        this._queue = []         // 执行队列
    }
}

然后我们回顾一下promise的使用:

const pro = new Promise((resolve, reject) => {
    setTimeout(_ => {
        resolve(123)
    }, 100)
})

pro.then((data) => {
    console.log(data)
}, (reason) => {
    console.log(reason)
})

好的,以上就是基本使用了。可以看到构造函数接受一个函数,该函数又接受两个函数,分别用来将promise的状态由推向成功,失败,并且这个过程是不可逆的。

现在我们再给之前的手写代码添加以上功能:

class Promise {
    constructor (executor) {
        this._state = 'pending'  // 状态
        this._value = undefined  // 数据
        this._queue = []         // 执行队列
        
        try {
            executor(this._resolve.bind(this),this._reject.bind(this))
        } catch (error) {
            this._reject(error)
        }
    }
    
    // 在原型上提供resolve和reject函数
    _resolve (data) {
        this._changeState(data, 'fufilled')
    }
    
    _reject (reason) {
        this._changeState(reason, 'rejected')
    }
    
    /**
    *   改变状态
    */
    _changeState (value, state) {
        // 如果当前状态已经发生改变,则直接终止后续流程
        if (this._state !== 'pending') {
            return;
        }
        this._state = state
        this._value = value
    }
    
    then (onFulfilled, onRejected) {
        return new MyPromise((resolve, reject) => {
            // 这个Promise的resolve或reject改什么时候调用呢?
        })
    }
}

接下来,我们想想,then函数该怎么实现呢?其实then函数就是promise的核心,实现后基本就完成了Promise A+规范啦!

then函数调用时,是直接运行传入的回调吗?如果不是,那是直接将他们放入微任务队列吗?其实也不是。

我们可以想想,如果是上述流程,那么我们是在100ms之后运行的resolve,而如果是直接放入微任务队列,那岂不是不受我们的resolve管控了,所以这里应该是将传入的回调放入我们之前的queue对列中去,等到当前的状态改变之后,再去将它们从我们维护的队列中取出来放入微任务队列。

ok,我们继续完善上述代码!

class MyPromise {
  constructor(executor) {
    this._state = "pending"; // 状态
    this._value = undefined; // 数据
    this._queue = []; // 执行队列

    try {
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      this._reject(error);
    }
  }

  // 在原型上提供resolve和reject函数
  _resolve(data) {
    this._changeState(data, "fufilled");
  }

  _reject(reason) {
    this._changeState(reason, "rejected");
  }

  /**
   *   改变状态
   */
  _changeState(value, state) {
    // 如果当前状态已经发生改变,则直接终止后续流程
    if (this._state !== "pending") {
      return;
    }
    this._state = state;
    this._value = value;
  }

  _pushHandlerToQueue(handler) {
    this._queue.push(handler);
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      // 这里我们不只是将处理函数传进去就可以了,还会将该函数是什么状态下执行的,还有改变下一个peromise状态的函数的相关信息也都放进去,以便后续在进行处理的时候,知道如何针对性处理。在这里我们不管用户传入的参数是否为一个函数,我们把这个判断及处理一并放在真正要处理的时候去处理。
      this._pushHandlerToQueue({
        executor: onFulfilled,
        state: "fulfilled",
        resolve,
        reject,
      });
      this._pushHandlerToQueue({
        executor: onRejected,
        state: "rejected",
        resolve,
        reject,
      });
    });
  }
}

接下来,我们看看什么时候对我们放入到队列中的各种处理函数进行真正的处理呢,在处理的时候,我们又应该如何对不同的情况进行针对性处理呢?

当我们调用改变状态函数时,我们需要去处理队列中的任务,这是毋庸置疑的;

还有一种情况,就是调用then函数,在将任务放进队列中后,也需要去处理,为什么呢?

因为如果说不处理,那么如果前面的改变状态函数是同步执行的,在处理的时候,队列中是没有任务的,然后,代码执行到then函数这里,也不进行处理的话,那么then函数接受的任务就只是放进维护的队列中,永远都不会去执行啦!

ok,明白这一点,我们继续完善代码,这一次,我们要解决两件事情:

  • 在resolve,then后去处理任务
  • 处理不同的任务
class MyPromise {
  constructor(executor) {
    this._state = "pending"; // 状态
    this._value = undefined; // 数据
    this._queue = []; // 执行队列

    try {
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      this._reject(error);
    }
  }

  // 在原型上提供resolve和reject函数
  _resolve(data) {
    this._changeState(data, "fufilled");
  }

  _reject(reason) {
    this._changeState(reason, "rejected");
  }

  /**
   *   改变状态
   */
  _changeState(value, state) {
    // 如果当前状态已经发生改变,则直接终止后续流程
    if (this._state !== "pending") {
      return;
    }
    this._state = state;
    this._value = value;
    // 改变状态后,处理任务
    this._runHandlers();
  }

  /**
   *  我们专门用一个函数来处理,提取公共代码
   */
  _runHandlers() {
    // 如果说状态还没改变,直接终止后续流程,只有在状态改变后,才能去处理任务
    if (this._state === "pending") {
      return;
    }

    while (this._queue.length) {
      // 循环取第一个任务进行处理
      this._runOneHandler(this._queue[0]);
      // 处理一个之后,需要移除掉,否则会重复拿出来执行
      this._queue.shift();
    }
  }

  /**
   *  这里专门提取一个函数来处理一个任务
   */
  _runOneHandler({ executor, state, resolve, reject }) {
    // 我们将任务放入微任务中去,这里假设我们已经实现好了这么一个函数,传入一个函数,就会将该函数放入微任务队列
    pushFuncToMicroTaskQueue((_) => {
      // 去掉不符合当前状态的任务
      if (this._state !== state) {
        return;
      }

      // 如果传入的不是一个函数,则将这次的数据和状态传递给下一个promise
      if (typeof executor !== "function") {
        this._state === "fulfilled" ? resolve(this._value) : reject(this._value);
        return;
      }

      try {
        const result = executor(this._value);
        // 这里要分为两种情况,
        // 1: 返回值为promise实例
        //    则根据这个实例来决定下一个promise的状态
        // 2: 返回值不为promise实例
        //    则直接将result作为下一个promise的已决数据传递进去
        // 这里假设已经实现了判断一个数据是否为promise实例
        if (isPromise(result)) {
          result.then(resolve, reject);
        } else {
          resolve(result);
        }
      } catch (error) {
        reject(error);
      }
    });
  }

  _pushHandlerToQueue(handler) {
    this._queue.push(handler);
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      // 这里我们不只是将处理函数传进去就可以了,还会将该函数是什么状态下执行的,还有改变下一个peromise状态的函数的相关信息也都放进去,以便后续在进行处理的时候,知道如何针对性处理。在这里我们不管用户传入的参数是否为一个函数,我们把这个判断即处理一并放在真正要处理的时候去处理。
      this._pushHandlerToQueue({
        executor: onFulfilled,
        state: "fulfilled",
        resolve,
        reject,
      });
      this._pushHandlerToQueue({
        executor: onRejected,
        state: "rejected",
        resolve,
        reject,
      });
      // 将任务添加到队列后,处理任务
      this._runHandlers();
    });
  }
}

ok,到此为止,其实我们已经实现了promise A+规范!

那么完了吗?别忘了,我们还有两个函数没有实现呢,其实这两个函数并不是我们实现promise的核心,就当个小插曲吧!

代码走起!

function pushFuncToMicroTaskQueue (cb) {
    // node环境
    if (typeof process === 'object' && typeof process.nextTick === 'function') {
        process.nextTick(cb)
    } else if (typeof MutaionObserver === 'function') {
        // 浏览器环境
        const div = document.createElement('div')
        const observer = new MutationObserver(cb)
        observer.observe(div, {
            attributes: true
        })
        div.setAttribute('a', 1)
    } else {
        // 实在没办法了,就用兼容性最好的远古级API setTimeout吧
        setTimeout(cb)
    }
}

function isPomise (target) {
    return (typeof target === 'object' || typeof target === 'function') && typeof target.then === 'function'
}

至此,我们已经完成了手写promise!

可是,这代码还差了点意思,我们将代码中的一些硬编码给解决掉,并且将一些内部使用的函数都进行隐藏,不给外界提供访问!

最后结果如下:

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

const _state = Symbol("state");
const _value = Symbol("value");
const _queue = Symbol("queue");
const _resolve = Symbol("resolve");
const _reject = Symbol("reject");
const _changeState = Symbol("changeState");
const _pushHandlerToQueue = Symbol("pushHandler");
const _runHandlers = Symbol("runHandlers");
const _runOneHandler = Symbol("runOneHandler");
function pushFuncToMicroTaskQueue(cb) {
  // node环境
  if (typeof process === "object" && typeof process.nextTick === "function") {
    process.nextTick(cb);
  } else if (typeof MutaionObserver === "function") {
    // 浏览器环境
    const div = document.createElement("div");
    const observer = new MutationObserver(cb);
    observer.observe(div, {
      attributes: true,
    });
    div.setAttribute("a", 1);
  } else {
    // 实在没办法了,就用兼容性最好的远古级API setTimeout吧
    setTimeout(cb);
  }
}

function isPomise(target) {
  return (typeof target === 'object' || typeof target === 'function') && typeof target.then === "function";
}

class MyPromise {
  constructor(executor) {
    this[_state] = PENDING; // 状态
    this[_value] = undefined; // 数据
    this[_queue] = []; // 执行队列

    try {
      executor(this[_resolve].bind(this), this[_reject].bind(this));
    } catch (error) {
      this[_reject](error);
    }
  }

  // 在原型上提供resolve和reject函数
  [_resolve](data) {
    this[_changeState](data, FULFILLED);
  }

  [_reject](reason) {
    this[_changeState](reason, REJECTED);
  }

  /**
   *   改变状态
   */
  [_changeState](value, state) {
    // 如果当前状态已经发生改变,则直接终止后续流程
    if (this[_state] !== PENDING) {
      return;
    }
    this[_state] = state;
    this[_value] = value;
    // 改变状态后,处理任务
    this[_runHandlers]();
  }

  /**
   *  我们专门用一个函数来处理,提取公共代码
   */
  [_runHandlers]() {
    // 如果说状态还没改变,直接终止后续流程,只有在状态改变后,才能去处理任务
    if (this[_state] === PENDING) {
      return;
    }

    while (this[_queue].length) {
      // 循环取第一个任务进行处理
      this[_runOneHandler](this[_queue][0]);
      // 处理一个之后,需要移除掉,否则会重复拿出来执行
      this[_queue].shift();
    }
  }

  /**
   *  这里专门提取一个函数来处理一个任务
   */
  [_runOneHandler]({ executor, state, resolve, reject }) {
    // 我们将任务放入微任务中去,这里假设我们已经实现好了这么一个函数,传入一个函数,就会将该函数放入微任务队列
    pushFuncToMicroTaskQueue((_) => {
      // 去掉不符合当前状态的任务
      if (this[_state] !== state) {
        return;
      }

      // 如果传入的不是一个函数,则将这次的数据和状态传递给下一个promise
      if (typeof executor !== "function") {
        this[_state] === FULFILLED ? resolve(this[_value]) : reject(this[_value]);
        return;
      }

      try {
        const result = executor(this[_value]);
        // 这里要分为两种情况,
        // 1: 返回值为promise实例
        //    则根据这个实例来决定下一个promise的状态
        // 2: 返回值不为promise实例
        //    则直接将result作为下一个promise的已决数据传递进去
        // 这里假设已经实现了判断一个数据是否为promise实例
        if (isPromise(result)) {
          result.then(resolve, reject);
        } else {
          resolve(result);
        }
      } catch (error) {
        reject(error);
      }
    });
  }

  [_pushHandlerToQueue](handler) {
    this[_queue].push(handler);
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      // 这里我们不只是将处理函数传进去就可以了,还会将该函数是什么状态下执行的,还有改变下一个peromise状态的函数的相关信息也都放进去,以便后续在进行处理的时候,知道如何针对性处理。在这里我们不管用户传入的参数是否为一个函数,我们把这个判断即处理一并放在真正要处理的时候去处理。
      this[_pushHandlerToQueue]({
        executor: onFulfilled,
        state: FULFILLED,
        resolve,
        reject,
      });
      this[_pushHandlerToQueue]({
        executor: onRejected,
        state: REJECTED,
        resolve,
        reject,
      });
      // 将任务添加到队列后,处理任务
      this[_runHandlers]();
    });
  }
}

大功告成!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容