JS高级:手写一个Promise

在 JavaScript 编程中,异步操作是一个至关重要的概念。随着 Web 应用的复杂性不断增加,处理异步任务变得愈发常见。Promise 作为一种处理异步操作的强大工具,在 JavaScript 开发者的日常工作中扮演着不可或缺的角色。不仅在工作,在面试中,不管是 大厂还是小厂,手写Promise已经基本成为面试的必考点,下面就让我们一起去了解Promise。

想要手写一个Promise,就必须要了解Promise A+规范,目前所有的Promise类库,都要遵守这个规范。

先写一个基础的Promise

new Promise 时需要传递一个执行器executor(),执行器会立即执行。

Promise 复制代码
 class CustPromise1 {
   constructor(executor){
     executor()//立即执行
   };
 };

 const cPromise1 = new CustPromise1((resolve, reject)=>{
   console.log("手撕Promise");
 });

为Promise添加resolve,reject 以及状态

  • promise有三个状态:pendingfulfilledrejected,分别是:等待, 完成, 拒绝。
  • 执行器需要接收两个参数:resolvereject
  • Promise的默认状态:pending

代码中我们分别定义三个常量来存储不同的状态:PROMISE_STATUS_PENDING:pending, PROMISE_STATUS_FULFILLED: fulfilled, PROMISE_STATUS_REJECTED:rejected。

当执行resolve或者reject方法回调时,必须要先判断当前的状态是否是pending,只有状态为初始状态时才可以执行。

CustPromise2 复制代码
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";

class CustPromise2 {
  constructor(executor){
    // 默认状态
    this.status = PROMISE_STATUS_PENDING;

    const resolve = ()=>{
    // 只有初始状态才允许执行,状态发生变化则锁死不执行
      if(this.status === PROMISE_STATUS_PENDING){
        // 改变状态:fulfilled
        this.status = PROMISE_STATUS_FULFILLED;
        console.log("Promise.resolve 被调用!")
      };
    };

  const reject = ()=>{
      // 只有初始状态才允许执行,状态发生变化则锁死不执行
    if(this.status === PROMISE_STATUS_PENDING){
      // 改变状态
      this.status = PROMISE_STATUS_REJECTED;
      console.log("Promise.reject 被调用!")
    };
  };

  executor(resolve, reject);
  };
};

const cPromise2 = new CustPromise2((resolve, reject)=>{
  console.log("pending状态");
  resolve(); // resolve 被调用后 初始状态发生改变 reject便不会再被调用,反之也如此。
  reject();
});

实现异步信息传递

异步信息的传递,离不开resolve,reject 方法去接收异步信息。因此,Promise需要有一个变量value用来保存成功状态的值,需要一个reason用来保存失败状态的值。

CustPromise3 复制代码
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";

class CustPromise3{
  constructor(executor){
    this.status = PROMISE_STATUS_PENDING;

    // 声明两个用于接收异步信息的变量
    this.value = undefined;
    this.reason = undefined;

    const resolve = (value)=>{

      // 只有初始状态才允许执行,状态发生变化则锁死不执行
      if(this.status === PROMISE_STATUS_PENDING){

        // 改变状态
        this.status = PROMISE_STATUS_FULFILLED;

        // 赋值异步信息
        this.value = value;

        console.log("resolve被调用")
      };
    };

    const reject = (reason)=>{

      // 只有初始状态才允许执行,状态发生变化则锁死不执行
      if(this.status === PROMISE_STATUS_PENDING){
        
        // 改变状态
        this.status = PROMISE_STATUS_REJECTED;

        // 赋值对应拒绝信息
        this.reason = reason;

        console.log("reject被调用")
      };
    };

    executor(resolve, reject);
  };
};

const cPromise3 = new CustPromise3((resolve, reject)=>{
  console.log("pending");
  resolve("success"); // resolve 被调用后 初始状态发生改变 reject便不会再被调用,反之也如此。
  reject("fail");
});

添加then方法,以及对应的执行顺序

Promise必须有一个then方法,该方法接收两个参数:onFulfilledonRejected

  • onFulfilled:调用then方法时,若执行成功,就执行onFulfilled方法,参数是Promise的value。
  • onRejected:调用then方法时,若执行失败,就执行onRejected方法,参数是Promise的reason。
CustPromise4 复制代码
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECDED = "rejected";

class CustPromise4 {
  constructor(executor){
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;

    const resolve = (value)=>{
      if(this.status === PROMISE_STATUS_PENDING){
        this.status = PROMISE_STATUS_FULFILLED;
        this.value = value;
        // this.onFulfilled(); //then方法接收resolve状态传递的信息
        // setTimeout(()=>this.onFulfilled(), 0);
        queueMicrotask(() => this.onFulfilled(value));
        console.log("resolve被调用");
      };
    };

    const reject = (reason)=>{
      if(this.status === PROMISE_STATUS_PENDING){
        this.status = PROMISE_STATUS_REJECDED;
        this.reason = reason;
        // this.onRejected(); //then方法接收reject状态传递的信息
        queueMicrotask(() => this.onRejected(reason));
        console.log("reject被调用")
      };
    };

    executor(resolve, reject);
  };

  // 编写then方法
  then(onFulfilled, onReject){
    
    this.onFulfilled = onFulfilled;
    this.onRejected = onReject;
  };
};

const cPromise4 = new CustPromise4((resolve, reject)=>{
  console.log("pending");
  resolve("success"); // resolve 被调用后 初始状态发生改变 reject便不会再被调用,反之也如此。
  reject("fail");
});

cPromise4.then((res)=>{
console.log("res", res);
},
(err)=>{
console.log("err", err);
},
);

如上代码所示 ,在resolve和reject方法执行时会调用 then 方法中提供两个方法,但是在调用Promise时,执行器executor里的内容会被立即执行。但是JS引擎是从上到下 的执行顺序,调用CustPromise4实例对象的then方法是注定会在new该实例对象的后面,所以会出现报错onFulfilled/onRejected is not function

如何解决上述问题呢?此刻我们可以引用Window中提供的queueMicrotask方法来处理。如果不想使用此方法,也可通过在then方法中传入参数的方式处理,如下所示

CustPromise4 复制代码
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECDED = "rejected";

class CustPromise4 {
  constructor(executor){
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;

    const resolve = (value)=>{
      if(this.status === PROMISE_STATUS_PENDING){
        this.status = PROMISE_STATUS_FULFILLED;
        this.value = value;
        console.log("resolve被调用");
      };
    };

    const reject = (reason)=>{
      if(this.status === PROMISE_STATUS_PENDING){
        this.status = PROMISE_STATUS_REJECDED;
        this.reason = reason;
        console.log("reject被调用")
      };
    };

    executor(resolve, reject);
  };

  // 编写then方法
  then(onFulfilled, onReject){

    if(this.status ===PROMISE_STATUS_FULFILLED){
        onFulfilled(this.value);
    };
    
    if(this.status ===PROMISE_STATUS_REJECDED){
        onReject(this.reason);
    };
  };
};


const cPromise4 = new CustPromise4((resolve, reject)=>{
  console.log("pending");
  resolve("success"); // resolve 被调用后 初始状态发生改变 reject便不会再被调用,反之也如此。
  reject("fail");
});

cPromise4.then((res)=>{
console.log("res", res);
},
(err)=>{
console.log("err", err);
},
);

对then方法持续优化,以及链式调用

CustPromise6 复制代码
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECDED = "rejected";

// 工具函数
function custPromiseCallbackErr(executor, value, resolve, reject) {
  try {
    const result = executor(value)
    resolve(result)
  } catch(err) {
    reject(err)
  }
}

class CustPromise6 {
  constructor(executor){
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;

    // 存放成功的回调
    this.onResolvedCallbacks = [];
    // 存放失败的回调
    this.onRejectedCallbacks = [];


    const resolve = (value)=>{
      if(this.status === PROMISE_STATUS_PENDING){
        this.status = PROMISE_STATUS_FULFILLED;
        this.value = value;
        queueMicrotask(()=>{
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.onResolvedCallbacks.forEach(fn=>fn(this.value))
        }); // 对累积的then调用兑现状态函数进行遍历分别调用
      };
    };

    const reject = (reason)=>{
      if(this.status === PROMISE_STATUS_PENDING){
        this.status = PROMISE_STATUS_REJECDED;
        this.reason = reason;
        queueMicrotask(()=>{
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.onRejectedCallbacks.forEach(fn=>fn(this.reason))
        }); // 对累积的then调用拒绝状态函数进行遍历分别调用
      };
    };


    try { 
      executor(resolve,reject) 
    } catch (error) { 
      reject(error) 
    }
  };

  // then方法
  // 将成功/失败的回调方法放入数组中 进行统一调用。
  then(onFulfilled, onReject){

    return new CustPromise6((resolve,reject)=>{

      // 如果在then调用的时候, 状态已经确定下来
      if(this.status === PROMISE_STATUS_FULFILLED && onFulfilled){
        custPromiseCallbackErr(onFulfilled, this.value, resolve,reject);
      };

      // 如果在then调用的时候, 状态已经确定下来
      if(this.status === PROMISE_STATUS_REJECDED && onReject){
        custPromiseCallbackErr(onFulfilled, this.reason, resolve,reject);
      };

      // 将成功回调和失败的回调放到数组中
      if(this.dstatus === PROMISE_STATUS_PENDING){
        this.onResolvedCallbacks.push(()=>{
          custPromiseCallbackErr(onFulfilled, this.reason, resolve,reject);
        });
        this.onRejectedCallbacks.push(()=>{
          custPromiseCallbackErr(onReject, this.reason, resolve,reject);
        });
      };

    });
  };
};

const cPromise6 = new CustPromise6((resolve, reject)=>{
  console.log("状态pending")
  resolve('成功调用resolve')
});

cPromise6.then(res => {
  console.log("res1:", res)
  return "11111"
}, err => {
  console.log("err1:", err)
  return "2222"
}).then(res => {
  console.log("res2:", res)
}, err => {
  console.log("err2:", err)
});

所有的代码块

相关推荐
文城5211 小时前
HTML-day1(学习自用)
前端·学习·html
阿珊和她的猫1 小时前
Vue 和 React 的生态系统有哪些主要区别
前端·vue.js·react.js
偷光1 小时前
深度剖析 React 的 useReducer Hook:从基础到高级用法
前端·javascript·react.js
The_era_achievs_hero2 小时前
动态表格html
前端·javascript·html
一包烟电脑面前做一天2 小时前
C#、.Net 中级高级架构管理面试题杂烩
面试·架构·c#·.net·面试题
Thomas_YXQ3 小时前
Unity3D Shader 简析:变体与缓存详解
开发语言·前端·缓存·unity3d·shader
傻小胖3 小时前
ES6 Proxy 用法总结以及 Object.defineProperty用法区别
前端·javascript·es6
Web极客码3 小时前
如何跟踪你WordPress网站的SEO变化
前端·搜索引擎·wordpress
横冲直撞de4 小时前
高版本electron使用iohook失败(使用uiohook-napi替代)
前端·javascript·electron
_Eden_4 小时前
认识Electron 开启新的探索世界一
前端·javascript·electron