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

所有的代码块

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
uhakadotcom8 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github