2025,给自己一个Promise吧

379 阅读7分钟
新年快乐!不知不觉,2024已经到了最后一步了,我们即将开启下一段的掘金,自加入这片热土,时常拜读各位的文章,收获良多趣味,也试着敲击自己的旋律,一概不知的小白,也有了一些成长,对前端,对开发的热情愈发旺盛,这也督促着我,持续学习。实践和记录是我觉得很好的学习方法,老师曾说,‘习惯和自律是产生差距的重要原因’,前段时间,自己总给自己找借口,太累了,工作忙等等,属实惭愧,为什么大家可以在深夜学习,而我又为何不能熬个半小时写个demo或是记录文章呢?25年,必须给自己一个Promise了。
来到今天的主题 ES6带来的Promise

那么请问,什么是promise

在此之前,由于JS是一门单线程的语言,当我们需要发起接口请求等操作时,由于项目的复杂性,不得不使用多重嵌套的回调函数来实现异步编程,然而这种写法可读性并不好而且容易造成回调地狱,那么我们也需要一个方法能够避免这种“俄罗斯套娃”的回调,用另一种方式来切契合这个场景,比如说链式调用,那么,顺应Promise/A+规范的Promise在ES6和大家见面了。

顾名思义,Promise就是承诺,承诺将来会执行。同时,它有三种状态,待定(Pending)已兑现(fulfilled)已拒绝(Reject),并且,只能从Pending变化到Resolve或者Rejected,一旦变更,就已经敲定(Settled),不会再变化了。 我们或多或少都了解过Promise,我们来回顾一些使用场景:

// 检查库存
function checkInventory(itemId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const availableItems = [1, 2, 3];
            availableItems.includes(itemId) ? resolve(`商品 ${itemId} 有库存`) : reject(`商品 ${itemId} 缺货`);
        }, 1000);
    });
}

// 处理支付
function processPayment(amount) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            Math.random() > 0.2 ? resolve(`支付成功,金额:${amount}`) : reject("支付失败");
        }, 1500);
    });
}

// 确认订单
function confirmOrder() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            Math.random() > 0.1 ? resolve("订单确认成功") : reject("订单确认失败");
        }, 1000);
    });
}

// 购物流程
function shop(itemId, amount) {
    checkInventory(itemId)
        .then(inventoryMessage => {
            console.log(inventoryMessage);
            return processPayment(amount);
        })
        .then(paymentMessage => {
            console.log(paymentMessage);
            return confirmOrder();
        })
        .then(orderMessage => {
            console.log(orderMessage);
        })
        .catch(error => {
            console.error("购物流程出错:", error);
        });
}

通过以上的例子相信对Promise特性有了一定的了解,那么我们试着去实现一下,加深对Promise的印象。

1. 构造函数 constructor

Promise 的构造函数是我们手写实现的起点,它需要接收一个执行器(executor)函数,这个函数通常包含两个参数:resolvereject,用来分别表示成功和失败的回调。执行器函数会立即执行,所以 Promise 一开始处于 pending(待定)状态。

  • 那么,我们需要一个构造函数执行 -> constructor()?
  • 能够接收两个参数resolve和reject -> 都是一个回调函数
  • 有初始状态 -> 每个promise需要定义一个状态变量,初始值为pending

那么resolve和reject,好像是可以直接调用的,所以在构造函数执行的时候,也会创建这两个方法。 我们先看resolve

  • 将状态改为fulfilled
  • 接受一个值value
  • 并且会执行某一个方法并且将值传递

resolve我们知道了,那么rejected也就照葫芦画瓢了。

2. then方法

其实接受两个参数

  • onFulfilled:Promise 成功时的回调函数,接收成功的值作为参数。
  • onRejected:Promise 失败时的回调函数,接收失败的原因作为参数。

默认值

在实际使用时,onFulfilledonRejected 并非必须传入,如果没有提供,则会采用默认的处理方式:

  • onFulfilled 默认返回原值,即 value => value
  • onRejected 默认抛出错误,即 reason => { throw reason },这可以确保异常能够被捕获并传递给链中的后续 .catch.then

我们再想想,

  • 当我们调用 resolvereject 时,参数可以是任何类型的值。这意味着我们需要在 then 中正确处理这些返回值,尤其是当值本身是另一个 Promise 时。
  • then方法也可以接受一个参数或一个方法,我们需要兼容两种传入。所以说,then方法会将方法保存起来,当resolve调用时才会触发? 什么时候存呢?
  • then是可以链式调用的,所以他返回的也是一个promise对象

所以

  • 状态管理

    • then 中,我们首先判断当前 Promise 的状态:

      • fulfilled:直接执行 onFulfilled 回调。
      • rejected:直接执行 onRejected 回调。
      • pending:如果当前 Promise 仍在待定状态,我们将 onFulfilledonRejected 保存到相应的回调队列中,待状态变化时再执行。
  • 异步执行(我们暂时先用setTimeOut(宏任务)模拟异步微任务):

    • 使用 setTimeout 将回调函数放入异步队列(微任务队列),确保 then 方法是异步执行的。
    • 这样做可以确保 then 方法的回调不会立即执行,而是等到当前执行栈清空后再执行,符合 JavaScript 异步行为的预期。
  • 返回新的 Promise

    • then 方法返回一个新的 Promise,以支持链式调用。
    • 当回调函数返回的是另一个 Promise 时,我们需要等待该 Promise 的完成(即调用它的 resolvereject),并根据其结果决定当前 then 的返回值。
  • 异常处理

    • 如果 onFulfilledonRejected 中的回调函数抛出异常,我们会捕获这个异常并将其传递给下一个链式调用。

那么我们可以写出这样一个代码:


class Promise {
    constructor(executor) {
        this.state = "pending";
        this.value = undefined;
        this.reason = undefined;
        this.onFullfilledCallbacks = []; 
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.state !== "pending") return
            this.state = "fulfilled";
            this.value = value;
            console.log("Resolved with value:", value); // 打印成功信息
            this.onFullfilledCallbacks.forEach((callback) => callback(value));
        };

        const reject = (reason) => {
            if (this.state !== "pending") return
            this.state = "rejected";
            this.reason = reason;
            console.log("Rejected with reason:", reason); // 打印错误信息
            this.onRejectedCallbacks.forEach((callback) => callback(reason));
        };

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled =
            typeof onFulfilled === "function" ? onFulfilled : (value) => value;
        onRejected =
            typeof onRejected === "function"
                ? onRejected
                : (reason) => {
                    throw reason;
                };
        return new Promise((resolve, reject) => {

        });
    }
    }
    catch(onRejected) {        
        return this.then(null, onRejected);
    }
}

那么这个返回的Promise中,是不是也会有不同的状态呢? 所以,我们还需要判断当前的promise状态,针对不同情况进行处理,所以刚刚还留了一点

  • 处理 Promise 的返回值

    • 如果 onFulfilledonRejected 的回调函数返回的是一个 Promise,我们需要确保新的 Promise 会等待这个 Promise 完成,然后继续执行。
    • 这就是为什么我们需要使用 instanceof Promise 来判断返回值是否是一个 Promise
  • 状态判断:我们需要判断当前 Promise 的状态是 fulfilledrejected 还是 pending。如果是前两者,我们直接调用相应的回调函数。如果是 pending,我们需要将回调函数推入一个队列,当状态发生变化时再调用。

  • 返回新的 Promise:我们必须确保 then 返回一个新的 Promise,这是为了链式调用的能力。并且我们需要考虑如何处理返回值(包括其他 Promise)。

        return new Promise((resolve, reject) => {
            if (this.state === "fulfilled") {

                setTimeout(() => {
                    try {
                        const result = onFulfilled(this.value);
                        resolve(result);
                    } catch (error) {
                        reject(error);
                    }
                },0);

            }
            if (this.state === "rejected") {

                setTimeout(() => {
                    try {
                        const result = onRejected(this.reason);
                        resolve(result);
                    }
                    catch (error) {
                        reject(error);
                    }
                },0);

            }
            if (this.state === "pending") {
                this.onFullfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const result = onFulfilled(this.value);
                            resolve(result);
                        } catch (error) {
                            reject(error);
                        }
                    },0);

                });
                this.onRejectedCallbacks.push(() => {

                    setTimeout(() => {
                        try {
                            const result = onRejected(this.reason);
                            resolve(result);
                        } catch (error) {
                            reject(error);
                        }
                    },0);
                });
            }
        });

那么到此,一个基本的Promise对象就已经搭建好了。

当然还没,还有catch和finally呢?那么我们想一想,这俩到底做了什么?

不难想到catch其实就是then的第二个回调。思考一下。因为他只关心reject的promise对吧。

image.png

那么finally呢?

是无论rejected或者resolve都会执行的,那么。也很简单吧。 在finally中,我们一般都会进行一些清理释放工作,比如loading状态的处理、数据的统一格式化等。所以和你想的一样吧。

finally(callback) {
    return this.then(
        (value) => {
            return Promise.resolve(callback()).then(() => value);
        },
        (reason) => {
            return Promise.resolve(callback()).then(() => {
                throw reason;
            });
        }
    );
}

Promise还未完结,正如我们对自己的要求和计划也刚刚开始,在我看来,Promise会结束,也不会结束。兄弟们,新年快乐!未来的日子一起努力!