「JS硬气功👊」Promise异步移形换影——上(我们一起暴打Promise手写题)

192 阅读6分钟

Hi! 这里是在县图书馆自习的JustHappy,既然点开了这个JS硬气功,那就一起愉快的修炼吧!我想大家和我一样,在面试中遇到Primise手写题这类的总是想不起来,所以这次我们尝试使用JS硬气功暴打一下,话不多说,我们开始吧。

由于篇幅原因,拆分为三篇供大家享用

JSpromise.jpeg

你还记得“回调函数”的本真吗?

什么是回调函数?

回调函数是一种编程模式,它允许我们将这个函数作为参数传递给另一个函数,并在某个操作完成时调用这个函数。

就像下面这样......(我们这里使用setTimeout去模拟异步的情况)

function fetchData(callback){
    setTimeout(() => {
        console.log("数据获取完成");
        callback("我*你*");
    }, 2000); // 模拟异步操作,2秒后完成
}

function showData(data){
    console.log("数据是:",data)
}

fetchData(showData)

是不是很简单,但是为什么我们在项目中很少这么写呢?

详情大家可以看看这篇「JS硬气功👊」从回调函数,迈一步两步走通JS异步

一切回归本源?我们为什么需要Promise?

在传统的JS异步编程中,通常使用回调函数来处理异步操作的结果。如果有多个异步操作需要按顺序执行,就会出现嵌套回调函数的情况,形成所谓的“回调地狱”。这种嵌套结构让代码难以阅读和维护就像下面这样。

只要嵌套的结构一深,我们就连函数的执行顺序都不知道是什么了,那同样的结构使用Promise的链式调用进行构造会是怎么样的呢?

doSomething()
  .then(result1 => doSomethingElse(result1))
  .then(result2 => doAnotherThing(result2))
  .catch(error => console.log('发生错误:', error));

是不是会好很多呢?那一个最基本的Promise用法是什么样子的呢?请看下面

const p1 = new Promise((resolve,reject) => {
    resolve(1000)
})

那如果有多个异步并发执行呢?这时候我们就需要使用Promise.all(),就像下面这样

Promise.all([asyncOp1(), asyncOp2(), asyncOp3()])
  .then(results => {
    console.log('所有操作完成:', results);
  })
  .catch(error => {
    console.log('某个操作失败:', error);
  });

那我们只需要知道这些并发异步中哪个异步先执行完,而不是有哪个异步操作失败呢?是吧,就像是这几个异步在race赛跑一样,我们使用Promise.race(),就像下面这样

const promise1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '第一个'));
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 200, '第二个'));
const promise3 = new Promise((resolve, reject) => setTimeout(resolve, 300, '第三个'));

Promise.race([promise1, promise2, promise3])
  .then(result => {
    console.log(result);  // '第一个'
  })
  .catch(error => {
    console.log(error);
  });

你可能也注意到了或者说回忆起了resolve和reject,其实Promise.resolve()Promise.reject()就是返回两种不同状态的Promise对象,resolve对应已解决(fulfilled) 而reject对应 已拒绝(rejected)

Promise.resolve('Hello, World!') .then(result => { 
    console.log(result); // 'Hello, World!' 
});

Promise.reject('Something went wrong')
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.log(error);  // 'Something went wrong'
  });

我们手写的Promise需要具备什么东西

通过上面的分析我们可以知道,一个相对完整的Promise需要具备以下几种基本功能

  1. 三种Promise状态反馈:pending、fulfilled、rejected、
  2. 链式调用.then().catch()
  3. .resolve().reject() 方法
  4. .all().race() 方法

那和我一起敲代码吧!

我们从构造函数开始

为了区分,我们就定义我们自己的Promise叫做MyPromise,回想一下我们是怎么使用Promise的?

const promise = new Promise((resolve, reject) => setTimeout(resolve, 100, 'xxx'));

我们使用new,那么我们的MyPromise就应该是一个Class,里面包含一个构造函数,传入一整个函数作为参数

class MyPromise{
    constructor(fn) {
        fn(a,b){
        ......
        }
    }
}

我们详细处理一下这两个参数哈,也就是一个resolve相关函数和一个reject相关的函数,我们使用resolveHandler和一个rejectHandler,那么就像下面这样

class MyPromise{
    constructor(fn) {
        const resolveHandler = (value) => {
        
        }
        const rejectHandler = (reason) => {
           
        }
        fn(resolveHandler,rejectHandler){
        }
    }
}

我们再想想,一个Promise对象还有什么要素?对的我们需要有一个属性去记录当前Promise的状态、还需要一个属性去记录Promise当前所携带的值(Value),我想你也会像下面这么写

class MyPromise{
    state = 'pending' // 最开始的状态是pending
    value = undefined // Promise返回成功后携带的值
    reason = undefined // Promise返回失败的原因
    constructor(fn) {
        const resolveHandler = (value) => {
        
        }
        const rejectHandler = (reason) => {
           
        }
        fn(resolveHandler,rejectHandler){
        }
    }
}

当然我们MyPromise的用户传入的是整个函数既fn,我们无法保证这个fn是否是正确的,所以我们可以使用try catch去测试一下,就像下面这样

class MyPromise{
    state = 'pending' // 最开始的状态是pending
    value = undefined // Promise返回成功后携带的值
    reason = undefined // Promise返回失败的原因
    constructor(fn) {
        const resolveHandler = (value) => {
        
        }
        const rejectHandler = (reason) => {
           
        }
        try{
            fn(resolveHandler,rejectHandler)           
        }catch{
            rejectHandler()
        }
    }
}

那我们的resolveHandler和一个rejectHandler应该怎么写的,很简单,就像下图一样,Promise的状态只能从pending到fulfilled或者从pending到rejected

image.png

那么我们就应该这样写

class MyPromise{
    state = 'pending' // 最开始的状态是pending
    value = undefined // Promise返回成功后携带的值
    reason = undefined // Promise返回失败的原因
    constructor(fn) {
        const resolveHandler = (value) => {
            if(this.state === 'pending'){
                this.state = 'fulfilled'
                this.value = value  // 将传入的value存储
            }
        }
        const rejectHandler = (reason) => {
            if(this.state === 'pending'){
                this.state = 'rejected'
                this.reason = reason // 将失败的原因存储
            }           
        }
        try{
            fn(resolveHandler,rejectHandler)           
        }catch{
            rejectHandler()
        }
    }
}

到此我们的MyPromise的构造函数算是基本上的结构是有了,如果要完全实现我们的构造函数,我们需要接着先写一下 Promise.then

浅浅看一下Promise.then

我们是如何使用Promise.then的?是不是下面这样,我们传入两个函数,一个成功后执行,一个失败后执行

// 使用 then 方法处理 Promise 的成功和失败
myPromise.then(
    (result) => {
        // 当 Promise 成功时,这里会被调用
        console.log("成功:", result); // 输出:成功: Promise 已成功完成!
    },
    (error) => {
        // 当 Promise 失败时,这里会被调用
        console.error("失败:", error);
    }
);

由于我们不知道当前的MyPromise的状态,所以我们需要判断当前的状态(😂),所以在我们知道状态之前我们只能将 .then传入的两个回调函数存储起来,只有在确认状态之后再对应执行,于是我们应该在MyPromise类中声明两个数组用于存放这两类回调函数(这边写的比较乱,需要大家理解理解,或者去看看别的教程)

那么目前就是下面这样

class MyPromise{
    state = 'pending' 
    value = undefined 
    reason = undefined 
    resolveCallbacks = [] // pending状态下,成功的回调
    rejectCallbacks = [] // pending状态下,失败的回调
    constructor(fn) {
         ......
    }
    // 这边以下是then
    then(fn1,fn2){
    }
}

什么时候执行resolveCallbacks中的东西呢?什么时候执行rejectCallbacks的呢?很显然是从 panding 变为 fullfilled 的时候是 resolveCallbacks ,从 pending 变为 rejectCallbacks 的时候是 rejectCallbacks,那我们就要在构造函数中操作,也就是修改resolveHandlerrejectHandler

就像下面这样

class MyPromise {
  state = "pending";
  value = undefined;
  reason = undefined;
  resolveCallbacks = [];
  rejectCallbacks = [];
  constructor(fn) {
    const resolveHandler = (value) => {
      if (this.state === "pending") {
        this.state = "fulfilled";
        this.value = value;
        // 遍历执行成功的回调
        this.resolveCallbacks.forEach((fn) => fn(this.value));
      }
    };
    const rejectHandler = (reason) => {
      if (this.state === "pending") {
        this.state = "rejected";
        this.reason = reason;
        // 遍历执行失败的回调
        this.rejectCallbacks.forEach((fn) => fn(this.reason));
      }
    };
    try {
      fn(resolveHandler, rejectHandler);
    } catch {
      rejectHandler();
    }
  }
  // 这边以下是then
  then(fn1, fn2) {}
}

ok,到这里我们的构造函数已经是基本完成了,我们可以试用一下

当resolve时候...... image.png

image.png

当reject的时候......

image.png

image.png

ok,这篇就到此为止,我们下篇来实现剩余的方法🚀🚀,像链式调用.all().race() 方法