手写Promise,让面试官看到不一样的答案

在网上看到了很多对promise的阐述手写promise,但是都不是很理想,于是决定自己写一篇

有人想过Promise是干什么的吗,可能会有部分人觉得是把一个函数放到异步队列里面执行,但是看过这篇文章之后相信你会对他有更深入的见解

需要手写代码的同学可以直接拉到最后,如有看不懂的可以评论区留言,哪里有错误欢迎指正,或者是代码结构书写如果你有不同意见可以在评论区留言一起讨论

首先要搞清楚一个知识需要明确两个问题

  1. 它的背景是什么
  2. 它是怎么实现的

了解他是干什么的 为了解决什么问题 没有这个东西会影响什么 背景不在本章考虑范围之内,很多文章都有对它的阐述,也非常好

那么接下来就来阐述他是怎么实现的,以及promise是什么东西

Promise 按照 Promise A+ 规范实现,为了保证知识的准确性,我们这边将边看文档边写

Promise A+规范英文文档

Promise A+规范中文文档

描述Promise

直接看规范是怎么阐述的

手写Promise,让面试官看到不一样的答案

3 4 5 暂时先不看

从这句可以得出两个点

  1. promise是一个对象或者是一个函数
  2. promise有一个then方法,并且这个then方法符合他的规范

我们这里实现promise使用对象的方式来实现Promise

这里首先看到术语的前两个,我用一行代码解释前两个表达的意思

手写Promise,让面试官看到不一样的答案

这里的Promise构造函数就是Promise 而这个构造函数返回的值就是thenable

也就是Promise构造函数里面定义了then函数那么这个构造函数就是Promise, 他返回的值就是thenable

从以上得知我们可以写一个构造函数MyPromise 构造函数里面有一个then 方法

手写Promise,让面试官看到不一样的答案

Promise的要求

手写Promise,让面试官看到不一样的答案

第一步描述了Promise的状态

三种状态,我们可以用三个常量表示

  • pending 待定
  • fulfilled 实现
  • rejected 拒绝

手写Promise,让面试官看到不一样的答案
状态是可以更改的,当是pending的时候可以变为fulfilled 或者是rejected

promise 这个待定就是表示对这个promise没有结果,当确定下来之后promise的状态只能是两种一个是fulfilled 另一个是rejected的,确定状态之后必须有一个值(undefined | null 也算是)

接下来就是实现状态和设置值

手写Promise,让面试官看到不一样的答案
我们这里参照v8的Promise,因为Promise只是描述你按照这个规范来写但是不管你怎么实现,你用函数实现也行用对象实现也行只要符合他的文档规范就行

官方的Promise是一个构造函数,需要你去new 并且有一个参数且为函数

手写Promise,让面试官看到不一样的答案

这里来看构造函数的三种情况

  1. 基础类型
  2. 对象
  3. 不传递
    手写Promise,让面试官看到不一样的答案

手写Promise,让面试官看到不一样的答案

手写Promise,让面试官看到不一样的答案

这里看到构造函数的参数只能是函数所以我们在*constructor*里面做一个判断使用new的时候必须传递一个函数

这个函数有两个参数

一个resolve 一个reject 这两个都是一个函数,第一个参数执行之后就会把这个这个promise的状态修改为成功,第二个则失败,里面可以传递一个参数作为成功之后的数据,失败之后的原因

手写Promise,让面试官看到不一样的答案
这个状态也只可以确定一次,当这个promise的状态更改过后则一切已经确定好了

手写Promise,让面试官看到不一样的答案

可以看到官方文档的这几句

大家看到下面这段话,不得更改,这个表示resolve(成功的值) | reject(失败原因) 传递过去的东西是不可以改变的,不可改变表示执行栈不可以改变,到时候我们把他单独存起来就行

大家看一下下面两张图片用来解释规范下面这段话

手写Promise,让面试官看到不一样的答案

手写Promise,让面试官看到不一样的答案

所以我们现在代码可以写为下面这种大家可以细看一下

手写Promise,让面试官看到不一样的答案

然后现在就考虑_resole 函数和_reject这两个函数

这个函数要做的事情也很简单

  1. 设置当前promise的状态
  2. 设置当前promise的值

我们这里调用一下之前写的函数就行

手写Promise,让面试官看到不一样的答案
调用executor过程中可能报错,官方的Promise是把当前的状态设置reject

这里我们简单用try catch包一下就行

手写Promise,让面试官看到不一样的答案

then

接下来就来实现then了,实现then我们要考虑一些问题参数,返回值

官方文档对then的描述,这里可能有点晦涩难懂,可以直接看下面我的描述

手写Promise,让面试官看到不一样的答案

从上面可以总结以下几点

  1. then的方法有两个参数,这两个参数的函数不能多次调用

  2. then方法必须返回一个promise

  3. 当promise状态确定下来之后才能调用这两个函数

    调用哪个,和怎么调用下文会详细说明这里暂时先不考虑

  4. 他的状态可以穿透

    如果不是一个函数则使用上一个的状态和值作为当前then返回的状态和数据

根据这两点我们来实现一下这个then

首先结构先搭建好

因为要进行链式调用所以这里我们可以再new 一次我们的Promise 这里他就返回一个pending的promise,这个promise携带then方法可以进行链式调用

手写Promise,让面试官看到不一样的答案

因为我们要等待状态确定好了之后才能执行这些方法,所以执行then的时候要把这些完成之后的函数,和失败之后的函数保存起来,等到状态确定下来的时候再从队列里面一个一个取出来进行操作(这里等待状态确定下来是指当前状态不为pending)所以我们这里维护一个队列

*constructor*里面定义一个处理队列

this._handles = []

在then执行的时候只需要把传递过来的函数添加到处理队列里面,因为我们要等状态确定了之后再判断要执行哪个函数,选择性执行,所以我们还要把当前的状态传递过去

以及设置当前的promise的状态,也就是then函数返回的这个promise,处理队列里面应该保存为一个对象

 {
    executor : Function,
    state : '状态',
    resolve,   //当executor执行成功了之后执行
    reject   // 当excutor执行失败了之后执行
}

这里有点绕,我们这里一个一个属性的详细解释

  1. executor,这个表示用户传递过来的数据,也就是then传递过来的参数,可选的,不传递就是undefined我们也把他放进去,或者是传递其他的值我们都把他放进去,因为等会要做状态穿透,只要参数不是一个函数,这个then函数返回的promise他的状态以及数据和上一个同步所以这里他传递什么东西都要给他放进去
  2. 这个state呢就表示我这个executor是成功之后执行还是失败之后执行,等状态确定下来了之后再做判断,筛选执行
  3. resolve和reject传递过去呢是为了设置在then函数里面new的我们自己的promise,因为现在还不能确定执行resolve还是执行reject等到将来处理队列的时候调用,executor没问题我们就执行resolve,他里面报错的话我们就执行reject

我们现在代码可以写为这种结构,下面是代码

手写Promise,让面试官看到不一样的答案

看到下面两句话

手写Promise,让面试官看到不一样的答案

手写Promise,让面试官看到不一样的答案

这里描述了执行then两个回调函数的过程需要把他放到异步,在浏览器可以通过MutationObserver 在node 通过process.nextTick

这里大家有兴趣可以点进去详细看一下,这里粗略过了直接上代码

大家可以把这段代码导出或者是直接写在这个文件里面

手写Promise,让面试官看到不一样的答案

下面是所有的代码

手写Promise,让面试官看到不一样的答案

下面代码用于复制,大家可以进行测试

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
​
const NODE = "node";
const BROWSER = "browser";
const OTHER = "other";
​
const env = (() => {
  if (typeof process === "object" && typeof process.nextTick === "function")
    return NODE;
  if (typeof MutationObserver === "function") {
    return BROWSER;
  }
  return OTHER;
})();
​
const microTask = {
  [NODE]: (callback) => {
    process.nextTick(callback);
  },
  [BROWSER]: (callback) => {
    const div = document.createElement("div");
    const observer = new MutationObserver(callback);
    observer.observe(div, {
      childList: true,
    });
    div.innerHTML = "1";
  },
  [OTHER]: (callback) => {
    setTimeout(callback, 0);
  },
};
​
/**
 * 添加一个函数到微队列里面
 * @param {Function} callback 任务函数
 */
function runMicroTask(callback) {
  microTask[env](callback);
}
​
/**
 * 判断函数是否为promise
 * @param {Object} obj
 */
function isPromise(obj) {
  return !!(obj && typeof obj === "object" && typeof obj.then === "function");
}
​
class MyPromise {
  /**
   *
   * @param {Function} executor 立即执行这个函数
   */
  constructor(executor) {
    if (typeof executor !== "function") {
      throw TypeError(`An argument is not a function`);
    }
    this._state = PENDING;
    this._value = undefined;
    this._handles = [];
    try {
      executor(this._resole.bind(this), this._reject.bind(this)); //这里需要使用bind绑定一下this,不然之后在resole里面使用this的话可能会出现问题造成参数访问不了,这个就不细讲了大家可以下去模拟一下
    } catch (exception) {
      this._reject(exception);
    }
  }
  /**
   * 设置promise的状态和值 状态和值只能修改一次
   * @param {String} state 状态
   * @param {any} value 值
   */
  _changeState(state, value) {
    if (this._state !== PENDING) return;
    this._state = state;
    this._value = value;
    this._runHandles()
  }
​
  /**
   * 取出处理队列里面的任务一个一个执行
   */
  _runHandles() {
    if (this._state === PENDING) return;
    while (this._handles[0]) {
      this._runOneHandles(this._handles[0]);
      this._handles.shift(); //每次执行完都要弹出去
    }
  }
  /**
   * 处理一项数据
   * @param {Object} param0
   */
  _runOneHandles({ executor, state, resolve, reject }) {
    runMicroTask(() => {
      if (state !== this._state) return;
      if (typeof executor !== "function") {
        state === FULFILLED ? resolve(this._value) : reject(this._value);
        return;
      }
      try {
        const resp = executor(this._value);
        if (isPromise(resp)) {
          resp.then(resolve, reject);
        } else {
          resolve(resp);
        }
      } catch (reason) {
        reject(reason);
      }
    });
  }
  /**
   * 设置当前promise的状态为成功 传递一个value为成功之后的数据
   * @param {any} value 成功的值
   */
  _resole(value) {
    this._changeState(FULFILLED, value);
  }
  /**
   * 调用这个函数设置promise的状态为失败 并且有一个失败的原因
   * @param {any} reason 失败的原因
   */
  _reject(reason) {
    this._changeState(REJECTED, reason);
  }
  /**
   * 向任务队列里面添加一项
   * @param {Function} executor 需要执行的函数
   * @param {String} state 当前这个函数是在什么状态下执行
   * @param {Function} resolve 设置当前promise为成功
   * @param {Function} reject 设置当前Promise为失败
   */
  _addOneHandles(executor, state, resolve, reject) {
    this._handles.push({
      executor,
      state,
      resolve,
      reject,
    });
  }
​
  /**
   * 可以多次调用
   * @param {Function} onFulfilled 完成之后执行的函数
   * @param {Function} onRejected 失败之后执行的函数
   */
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this._addOneHandles(onFulfilled, FULFILLED, resolve, reject);
      this._addOneHandles(onRejected, REJECTED, resolve, reject);
      this._runHandles()
    });
  }
}
​
​

里面有一些内部使用的方法,你可以在使用defineProperty控制一下

我们实现了简单的Promise,可以看到其实做的事情就是任务的调度,对一系列函数进行管控,当处于什么状态执行什么函数

面试题

这些题都可以使用上面我们自己手写的Promise进行分析

答案我这边不会给出,大家可以敲敲,验证一下结果,这些题做完可以说你对Promise已经有更深的见解了

手写Promise,让面试官看到不一样的答案

手写Promise,让面试官看到不一样的答案

手写Promise,让面试官看到不一样的答案

手写Promise,让面试官看到不一样的答案

麻烦点赞收藏加关注后续更新await到底在干什么

原文链接:https://juejin.cn/post/7332735436324929590 作者:无敌暴龙

(0)
上一篇 2024年2月9日 下午4:27
下一篇 2024年2月9日 下午4:38

相关推荐

发表回复

登录后才能评论