深入浅出Promise:从理论到实践,手写你的JavaScript异步解决方案

210 阅读6分钟

Promise

何为Promise

  • Promise的英文解释意为承诺,它在JS中可以用来存储一个值,并确保在你需要将这个值返回。这一点听上去似乎也并没有什么新奇的地方,任何一个对象都可以存储值,为什么非要使用Promise呢?Promise的特殊之处在于它提供了一种更优化的方式来处理异步操作。在传统的JavaScript编程中,处理异步操作(如网络请求或读取文件)通常会使用回调函数。然而,回调函数的嵌套和管理往往会导致代码可读性和可维护性的问题,即所谓的"回调地狱"。而Promise通过引入一套规范和语法,使得异步操作的处理更加简洁、清晰和可组合。

创建一个Promise

  • 在创建一个Promise时,我们需要传递一个构造器,它通常是一个回调函数。构造器接受两个参数,resolvereject,它们分别是用于成功和失败时的回调函数。
const promise = new Promise((resolve,reject)=>{
    // 在这里编写异步操作的代码
    // 当异步操作成功完成时,调用resolve函数,并传递结果
  	// 当异步操作失败时,调用reject函数,并传递错误信息
})
  • 当我们调用resolve函数传递一个结果时,该Promise的状态会由pending转为fulfilled
  • 当我们调用reject函数传递一个结果时,该Promise的状态会由pending转为rejected

Promise三种状态

在Promise中有一个表示状态的属性,它记录Promise执行的状态

  • pendin(进行中):该状态意味着操作仍在进行中,同时也是promise实例的初始状态。
  • fulfilled(成功):该状态意味着操作已经被接受、操作成功完成。当操作完成时promise实例的then()方法会被调用。
  • rejected(失败):该状态意味着操作失败,当操作失败时promise实例的catch()方法会被调用。

Promise实例方法

  1. then():该方法用于指定当Promise状态变为已完成(fulfilled)时和当Promise状态变为已拒绝(rejected)时的回调函数。它接受两个参数,第一个参数是当Promise状态变为已完成时执行的回调函数,第二个参数是当Promise状态变为已拒绝时执行的回调函数。这两个参数都是可选的。

  2. catch():该方法用于指定当Promise状态变为已拒绝时的回调函数。它接受一个参数,即当Promise状态变为已拒绝时执行的回调函数。它相当于调用then(null, onRejected),用于捕获Promise链中的错误。

  3. finally():该方法用于指定无论Promise状态最终变为已完成还是已拒绝时都会执行的回调函数。它接受一个参数,即无论Promise状态最终如何都会执行的回调函数。通常用于清理资源或执行一些收尾的操作。

Promise静态方法

  1. Promise.resolve() 创建一个立即完成的Promise
  2. Promise.reject() 创建一个立即拒绝的Promise
  3. Promise.all([...]) 同时返回多个Promise的执行结果,其中有一个拒绝,就返回拒绝
  4. Promise.allSettled([...]) 同时返回多个Promise的执行结果(无论成功或失败)
  5. Promise.race([...]) 多个promise同时执行,谁快就返回谁的结果(无论成功或失败)
  6. Promise.any([...]) 多个promise同时执行,谁先返回成功结果就是谁的结果(如何全为失败则为失败)

Promise的执行原理

  • Promise在执行,then就相当于给Promise的回调函数
    • 当Promise的状态从pending变为fulfilled时,
      then的回调函数会被放入到任务队列(微任务队列)中
  • Promise在机制依赖于事件循环机制

事件循环机制

  • JS是单线程的,它运行时基于事件循环机制(event loop)
    • 调用栈
      • 栈是一种后进先出的数据结构
      • 调用栈放的是要执行的代码
    • 任务队列(消息队列)
      • 队列
        • 队列是一种先进先出的数据结构
      • 当调用栈中的代码执行完毕后,队列中的代码才会按照顺序依次进入栈中执行
      • 在JS中有两种主要的任务队列
        • 宏任务队列
          大部分事件任务都会去宏任务队列排队
        • 微任务队列
          Promise中的回调函数(then,catch,finally)
          queueMicrotask() 用来向微任务队列中添加一个任务
    • 整个流程
      1. 先执行调用栈中的代码
      2. 执行微任务队列中的所有任务
      3. 执行宏任务队列中的所有任务

下面是MyPromise实现的一个逐步分解和重写,旨在提高可读性和理解性。我会尽量保持每一步的逻辑清晰,并解释每部分的作用。

尝试手写基础的Promise对象

定义基础结构

首先,我们定义MyPromise类的基本结构,包括状态、结果以及回调数组。

class MyPromise {
    constructor(executor) {
        this.#state = 'pending'; // 初始状态为pending
        this.#result = undefined; // 结果初始未定义
        this.#callbacks = []; // 回调数组初始化为空

        // 立即执行executor函数,传递resolve和reject方法
        try {
            executor(this.#resolve.bind(this), this.#reject.bind(this));
        } catch (error) {
            // 如果executor直接抛出错误,则认为promise被reject
            this.#reject(error);
        }
    }

    #resolve = (value) => { /* 实现待定 */ };
    #reject = (reason) => { /* 实现待定 */ };

    // then方法待实现
    then(onFulfilled, onRejected) { /* 实现待定 */ }
}

实现resolve和reject方法

接下来,实现#resolve#reject方法,处理状态变更及回调执行逻辑。

#resolve(value) {
    if (this.#state !== 'pending') return; // 非pending状态直接返回

    this.#state = 'fulfilled'; // 修改状态为fulfilled
    this.#result = value; // 保存结果

    // 微任务队列中执行所有成功回调
    queueMicrotask(() => {
        this.#callbacks.filter(cb => cb.onFulfilled).forEach(cb => cb.onFulfilled(value));
    });
}

#reject(reason) {
    if (this.#state !== 'pending') return; // 非pending状态直接返回

    this.#state = 'rejected'; // 修改状态为rejected
    this.#result = reason; // 保存原因

    // 微任务队列中执行所有失败回调
    queueMicrotask(() => {
        this.#callbacks.filter(cb => cb.onRejected).forEach(cb => cb.onRejected(reason));
    });
}

改进then方法以处理回调和链式调用

最后,完善then方法,确保它可以正确处理回调函数,并返回一个新的Promise以便链式调用。

then(onFulfilled, onRejected) {
    // 默认处理函数,避免undefined情况
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

    // 创建一个新的Promise来处理链式调用
    let nextPromise = new MyPromise((resolve, reject) => {
        // 根据当前状态立即或延迟执行回调
        if (this.#state === 'fulfilled') {
            queueMicrotask(() => {
                try {
                    let result = onFulfilled(this.#result);
                    resolve(result); // 解析下一个Promise
                } catch (error) {
                    reject(error); // 如果onFulfilled抛出错误,则reject下一个Promise
                }
            });
        } else if (this.#state === 'rejected') {
            queueMicrotask(() => {
                try {
                    let result = onRejected(this.#result);
                    resolve(result);
                } catch (error) {
                    reject(error);
                }
            });
        } else { // pending状态
            // 存储回调,等待状态改变时执行
            this.#callbacks.push({
                onFulfilled: value => {
                    try {
                        let result = onFulfilled(value);
                        resolve(result);
                    } catch (error) {
                        reject(error);
                    }
                },
                onRejected: reason => {
                    try {
                        let result = onRejected(reason);
                        resolve(result);
                    } catch (error) {
                        reject(error);
                    }
                }
            });
        }
    });

    return nextPromise;
}