带你手写一下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]();
    });
  }
}

大功告成!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容