手写Promise

promise对于前端来说用的最多的场景应该就是异步请求了

我参考大佬的文章自己写了一遍,每一步后面加入自己理解的小结

梳理完一遍后还是有很多收获的!😊

开始实现

一、核心逻辑实现

1.新建MyPromise类

js 复制代码
// 新建MyPromise类
class MyPromise {
    constructor(executor){
    // executor是一个执行器,进入会立即执行
        executor()
    }
}

2.executor 传入 resolve 和 reject 方法

js 复制代码
class MyPromise {
    // 传入一个函数里面含有两个参数resolve和reject
    constructor(executor){
        executor(this.resolve,this.reject)
    }
    // resolve和reject在调用的时候执行
    resolve = ()=>{}
    reject = ()=>{}
}

3.状态与结果的管理

js 复制代码
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    // 传入一个函数里面含有两个参数resolve和reject
    constructor(executor){
        executor(this.resolve,this.reject)
    }
    // 储存状态的变量,初始值是 pending
    status = PENDING;
    // 成功之后的值 
    value = null; 
    // 失败之后的原因 
    reason = null;
    // resolve和reject在调用的时候执行
    // 更改成功后的状态
    resolve = (value)=>{
        // 只有状态是等待,才执行状态修改
        if(this.status===PENDING){
            // 状态修改为成功
            this.status = FULFILLED
            // 保存成功之后的值
            this.value = value
        }
    }
    // 更改失败后的状态
    reject = (reason)=>{
        // 只有状态是等待,才执行状态修改
        if(this.status===PENDING){
            // 状态修改为成功
            this.status = REJECTED
            // 保存成功之后的值
            this.reason = reason
        }
    }
}
  1. then 的简单实现
js 复制代码
    then(onFulfilled, onRejected) { 
        if (this.status === FULFILLED) { 
            // 调用成功回调,并且把值返回 
            onFulfilled(this.value); } 
        else if (this.status === REJECTED) { 
            // 调用失败回调,并且把原因返回 
            onRejected(this.reason); 
    } }

5.使用 module.exports 对外暴露 MyPromise 类

js 复制代码
// MyPromise.js 
module.exports = MyPromise;

小结:

这里写了一个声明了几个变量

一个是promise类的构造函数需要一个参数,类型是函数,这个函数传参是resolve和reject

还声明了当前异步状态,成功和失败的值

后面实现了resolve和reject主要功能就是改变当前状态,然后存储数据

最后声明了一个then函数,传参是两个函数,第一个是成功的,第二个是失败,根据当前status状态,判断执行哪个函数

二、异步逻辑

上面实现了同步的成功函数、失败函数,和 then函数,但是如果有异步进程,那么promise函数还没有执行结果,then函数就会执行,这是有问题的,所以实现一下then的等待过程

js 复制代码
class MyPromise {
    // 传入一个函数里面含有两个参数resolve和reject
    constructor(executor){
        executor(this.resolve,this.reject)
    }
    
     // ====== 新增 ====== 
    // 存储成功回调函数 
    onFulfilledCallback = null; 
    // 存储失败回调函数 
    onRejectedCallback = null;
   
    // resolve和reject在调用的时候执行
    // 更改成功后的状态
    resolve = (value)=>{
        // 只有状态是等待,才执行状态修改
        if(this.status===PENDING){
            // 状态修改为成功
            this.status = FULFILLED
            // 保存成功之后的值
            this.value = value
            // ==== 新增 ==== 
            // 判断成功回调是否存在,如果存在就调用 
            this.onFulfilledCallback && this.onFulfilledCallback(value);
        }
    }
    // 更改失败后的状态
    reject = (reason)=>{
        // 只有状态是等待,才执行状态修改
        if(this.status===PENDING){
            // 状态修改为成功
            this.status = REJECTED
            // 保存成功之后的值
            this.reason = reason
            
            // ====== 新增 ====== 
            // 判断失败回调是否存在,如果存在就调用 
            this.onRejectedCallback && this.onRejectedCallback(reason)
        }
    }
}

then中的函数修改

js 复制代码
    then(onFulfilled, onRejected) { 
    // 判断状态 
    if (this.status === FULFILLED) { 
        // 调用成功回调,并且把值返回 
        onFulfilled(this.value); 
    } else if (this.status === REJECTED) { 
        // 调用失败回调,并且把原因返回 
        onRejected(this.reason); 
    } else if (this.status === PENDING) { 
        // ==== 新增 ==== 
        // 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来 
        // 等到执行成功失败函数的时候再执行
        this.onFulfilledCallback = onFulfilled; 
        this.onRejectedCallback = onRejected; 
    } }

小结:

这里是怎么保证then在promise函数之后执行的呢?

我理解的是一上来先执行的就是then函数,因为一上来就是pending状态,所以存储then回调函数中传入的两个函数参数,然后在promise实例中的resolve和reject执行,实现then中的回调函数在promise之后执行

三、多个异步逻辑

上面实现了单个异步进程,如果存在多个then函数,该怎么处理异步呢?

js 复制代码
// 存储成功回调函数
// onFulfilledCallback = null; 
onFulfilledCallbacks = []; 
// 存储失败回调函数 
// onRejectedCallback = null; 
onRejectedCallbacks = [];

在then函数的相应位置修改

js 复制代码
// ==== 新增 ==== 
// 因为不知道后面状态的变化,这里先将成功回调和失败回调存储起来 
// 等待后续调用 
this.onFulfilledCallbacks.push(onFulfilled); 
this.onRejectedCallbacks.push(onRejected);

在promise成功和失败的函数中,分别执行下面程序

js 复制代码
// resolve里面将所有成功的回调拿出来执行 
// 成功
while (this.onFulfilledCallbacks.length) { 
   this.onFulfilledCallbacks.shift()(value) 
}
// 失败
while (this.onRejectedCallbacks.length) { 
    this.onRejectedCallbacks.shift()(reason) 
}

小结:

这里就是用数组存储要执行的函数,然后利用数组方法shift,依次执行。

当然,这里默认的都是同步函数,后面增加对异步函数的处理

★☆★☆★☆★☆★☆★☆在这里可以先执行下已完成的内容试试★☆★☆★☆★☆★☆★☆

四、实现链式调用

js 复制代码
class MyPromise{
    ......
    then(onFulfilled,onRejected){
        // ===新增===
        // 为了链式调用这里直接创建一个 MyPromise,并在后面return出去
        const promise2 = new MyPromise((resolve,reject)=>{
            // 这里的内容在执行器中,会立即执行 
            if (this.status === FULFILLED) { 
                // 获取成功回调函数的执行结果 
                const x = onFulfilled(this.value); 
                // 传入 resolvePromise 集中处理 
                resolvePromise(x, resolve, reject);
            } 
            else if (this.status === REJECTED) { 
                onRejected(this.reason); 
            } 
            else if (this.status === PENDING) { 
                this.onFulfilledCallbacks.push(onFulfilled);
                this.onRejectedCallbacks.push(onRejected); 
            }
        })
        return promise2;
    }
}

function resolvePromise(x, resolve, reject) { 
    // 判断x是不是 MyPromise 实例对象 
    if(x instanceof MyPromise) { 
        // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected 
        // x.then(value => resolve(value), reason => reject(reason)) 
        // 简化之后 
        x.then(resolve, reject) } 
    else{ 
        // 普通值 
        resolve(x) 
    } }

小结:

这里实现的链式调用的逻辑是

在then函数里面声明一个promise,并且在then里面返回这个promise,就可以实现链式调用

在这里声明了一个新的函数resolvePromise,传入的x也就是当前的then中的执行结果,成功的回调函数,失败的回调函数,如果是promise实例,就接着执行then函数,否则就存储当前值

五、then方法链式调用识别Promise是否返回自己

js 复制代码
// MyPromise.js

class MyPromise {
  ......
  then(onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // ==== 新增 ====
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          // 获取成功回调函数的执行结果
          const x = onFulfilled(this.value);
          // 传入 resolvePromise 集中处理
          resolvePromise(promise2, x, resolve, reject);
        })  
      } else if (this.status === REJECTED) {
      ......
    }) 
    
    return promise2;
  }
}
js 复制代码
function resolvePromise(promise2, x, resolve, reject) { 
    // 如果相等了,说明return的是自己,抛出类型错误并返回 
    if (promise2 === x) { 
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>')) 
    } 
    if(x instanceof MyPromise) { 
        x.then(resolve, reject) 
    } 
    else{ 
        resolve(x) 
    } 
}

学习一个新的api

小结:

这一部分主要是传入当前promise及返回的promise,判断是否相等,

如果相等的话,就抛出异常,避免循环调用。 这里用了queueMicrotask这个api,创建一个微任务,等待当前任务队列的执行结束后,立马执行微任务队列的内容,

具体到这里也就是,等待promise2这个初始化结束后,再传入到resolvePromise里面

六、捕获错误及 then链式调用其他状态代码补充

  1. 捕获执行器错误
js 复制代码
// MyPromise.js

constructor(executor){
  // ==== 新增 ====
  // executor 是一个执行器,进入会立即执行
  // 并传入resolve和reject方法
  try {
    executor(this.resolve, this.reject)
  } catch (error) {
    // 如果有错误,就直接执行 reject
    this.reject(error)
  }
}

2.then 执行的时错误捕获

js 复制代码
// MyPromise.js

then(onFulfilled, onRejected) {
  // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
  const promise2 = new MyPromise((resolve, reject) => {
    // 判断状态
    if (this.status === FULFILLED) {
      // 创建一个微任务等待 promise2 完成初始化
      queueMicrotask(() => {
        // ==== 新增 ====
        try {
          // 获取成功回调函数的执行结果
          const x = onFulfilled(this.value);
          // 传入 resolvePromise 集中处理
          resolvePromise(promise2, x, resolve, reject);
        } catch (error) {
          reject(error)
        }  
      })  
    } else if (this.status === REJECTED) {
      // 调用失败回调,并且把原因返回
      onRejected(this.reason);
    } else if (this.status === PENDING) {
      // 等待
      // 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
      // 等到执行成功失败函数的时候再传递
      this.onFulfilledCallbacks.push(onFulfilled);
      this.onRejectedCallbacks.push(onRejected);
    }
  }) 
  
  return promise2;
}

小结:

这里主要是增加try...catch错误捕获机制,比较简单

测试一下

七、补全reject和pending中的逻辑

js 复制代码
// MyPromise.js

then(onFulfilled, onRejected) {
  // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
  const promise2 = new MyPromise((resolve, reject) => {
    // 判断状态
    if (this.status === FULFILLED) {
      // 创建一个微任务等待 promise2 完成初始化
      queueMicrotask(() => {
        try {
          // 获取成功回调函数的执行结果
          const x = onFulfilled(this.value);
          // 传入 resolvePromise 集中处理
          resolvePromise(promise2, x, resolve, reject);
        } catch (error) {
          reject(error)
        } 
      })  
    } else if (this.status === REJECTED) { 
      // ==== 新增 ====
      // 创建一个微任务等待 promise2 完成初始化
      queueMicrotask(() => {
        try {
          // 调用失败回调,并且把原因返回
          const x = onRejected(this.reason);
          // 传入 resolvePromise 集中处理
          resolvePromise(promise2, x, resolve, reject);
        } catch (error) {
          reject(error)
        } 
      }) 
    } else if (this.status === PENDING) {
      // 等待
      // 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
      // 等到执行成功失败函数的时候再传递
      this.onFulfilledCallbacks.push(() => {
        // ==== 新增 ====
        queueMicrotask(() => {
          try {
            // 获取成功回调函数的执行结果
            const x = onFulfilled(this.value);
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error)
          } 
        }) 
      });
      this.onRejectedCallbacks.push(() => {
        // ==== 新增 ====
        queueMicrotask(() => {
          try {
            // 调用失败回调,并且把原因返回
            const x = onRejected(this.reason);
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error)
          } 
        }) 
      });
    }
  }) 
  
  return promise2;
}

小结:

这里pending状态push函数时,添加了一个queueMicrotask函数,我测试了一下,如果这个函数里面存在微任务的话,是可以保证微任务执行完成之后再执行后续的then函数的,至于其他的用途,我需要再慢慢研究

测试一下

八、细节完善

  1. then中的参数变为可选
js 复制代码
// MyPromise.js

then(onFulfilled, onRejected) {
  // 如果不传,就使用默认函数
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};

  // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
  const promise2 = new MyPromise((resolve, reject) => {
  ......
}
  1. 实现 resolve 与 reject 的静态调用
js 复制代码
// MyPromise.js

MyPromise {
  ......
  // resolve 静态方法
  static resolve (parameter) {
    // 如果传入 MyPromise 就直接返回
    if (parameter instanceof MyPromise) {
      return parameter;
    }

    // 转成常规方式
    return new MyPromise(resolve =>  {
      resolve(parameter);
    });
  }

  // reject 静态方法
  static reject (reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }
}

小结:

这里就是添加一些细节及静态函数,很好理解

完结撒花了🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸

总结:

前路漫漫,这只是一小部分,先告一段落。常看常新!

相关推荐
阿伟来咯~5 分钟前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端10 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱13 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai22 分钟前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨23 分钟前
在JS中, 0 == [0] 吗
开发语言·javascript
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
aPurpleBerry3 小时前
JS常用数组方法 reduce filter find forEach
javascript
ZL不懂前端3 小时前
Content Security Policy (CSP)
前端·javascript·面试