手写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);
    });
  }
}

小结:

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

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

总结:

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

相关推荐
小华同学ai5 分钟前
vue-office:Star 4.2k,款支持多种Office文件预览的Vue组件库,一站式Office文件预览方案,真心不错
前端·javascript·vue.js·开源·github·office
问道飞鱼17 分钟前
【前端知识】强大的js动画组件anime.js
开发语言·前端·javascript·anime.js
k093319 分钟前
vue中proxy代理配置(测试一)
前端·javascript·vue.js
若川1 小时前
Taro 源码揭秘:10. Taro 到底是怎样转换成小程序文件的?
前端·javascript·react.js
IT女孩儿2 小时前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
@解忧杂货铺6 小时前
前端vue如何实现数字框中通过鼠标滚轮上下滚动增减数字
前端·javascript·vue.js
真的很上进11 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
噢,我明白了14 小时前
同源策略:为什么XMLHttpRequest不能跨域请求资源?
javascript·跨域
sanguine__15 小时前
APIs-day2
javascript·css·css3
关你西红柿子15 小时前
小程序app封装公用顶部筛选区uv-drop-down
前端·javascript·vue.js·小程序·uv