源码解读——Promise

各位小伙伴们,今天我们来实现一个面试中经常会被问到的内容------手写实现一个简版的 Promise。通过这个过程,不仅能够加深大家对 Promise 的理解,还能掌握其底层机制。

在这篇文章中,我们将一步步手写一个自定义的 MyPromise,通过代码讲解,帮助大家深入理解 Promise 的执行逻辑。我们将从基础的 Promise 定义、状态管理、then 方法的实现、回调函数收集等方面入手,逐步构建一个符合 Promise/A+ 规范的 Promise

什么是 Promise

首先,简单介绍一下 Promise。它是 JavaScript 用来处理异步操作的一种方式,它可以将异步代码转换为类似同步的形式。Promise 有三种状态:

  1. pending(等待中):初始状态,既没有被解决也没有被拒绝。
  2. fulfilled(已完成):操作成功完成。
  3. rejected(已拒绝):操作失败。

一个 Promise 的状态一旦从 pending 变为 fulfilledrejected,就不能再次更改。

接下来,我们开始手写一个 Promise 类,命名为 MyPromise

构建基础的 MyPromise

我们先从基本的构造函数入手。Promise 的构造函数接收一个执行器(executor),executor 是一个包含两个参数 resolvereject 的函数。我们需要在构造函数中定义 state 来表示 Promise 的当前状态,并且初始状态应该是 pending

js 复制代码
class MyPromise {
    constructor(executor) {
        // 定义初始状态为 pending
        this.state = 'pending';
        // 保存 resolve 和 reject 的值
        this.value = undefined;
        this.reason = undefined;
        
        // 用于存储 .then 和 .catch 中的回调函数
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];

        // 定义 resolve 函数
        const resolve = (val) => {
            if (this.state === 'pending') {
                // 将状态改为 fulfilled
                this.state = 'fulfilled';
                // 保存 resolve 的值
                this.value = val;
                // 依次执行 .then 中的回调
                this.onFulfilledCallbacks.forEach(fn => fn());
            }
        };

        // 定义 reject 函数
        const reject = (val) => {
            if (this.state === 'pending') {
                // 将状态改为 rejected
                this.state = 'rejected';
                // 保存 reject 的值
                this.reason = val;
                // 依次执行 .catch 中的回调
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        };

        // 调用 executor,并传入 resolve 和 reject
        executor(resolve, reject);
    }
}

我们在构造函数里定义了 this.state,初始值为 pendingresolve 函数的作用是,当调用时将 statepending 变为 fulfilled,并保存传入的 val

onFulfilledCallbacksonRejectedCallbacks 是用来存储 thencatch 中的回调函数,当 resolvereject 被调用时依次执行。

实现 then 方法

接下来,我们实现 then 方法,它接收两个回调函数------一个是成功时执行的 onFulfilled,另一个是失败时执行的 onRejected

js 复制代码
then(onFulfilled, onRejected) {
    // 检查 onFulfilled 和 onRejected 是否为函数
    if (onFulfilled && typeof onFulfilled !== 'function' || onRejected && typeof onRejected !== 'function') {
        throw new Error('then 必须接受一个函数');
    }

    // 返回一个新的 MyPromise 实例,实现链式调用
    return new MyPromise((resolve, reject) => {
        // 如果当前状态是 fulfilled,则执行 onFulfilled 回调
        if (this.state === 'fulfilled') {
            setTimeout(() => {
                try {
                    const result = onFulfilled(this.value);  // 传入 resolve 的值
                    resolve(result);  // 返回的值传递给下一个 then
                } catch (error) {
                    reject(error);  // 如果出错,捕获并传递给下一个 catch
                }
            });
        }

        // 如果当前状态是 rejected,则执行 onRejected 回调
        if (this.state === 'rejected') {
            setTimeout(() => {
                try {
                    const result = onRejected(this.reason);  // 传入 reject 的原因
                    resolve(result);
                } catch (error) {
                    reject(error);
                }
            });
        }

        // 如果当前状态是 pending,则将回调存起来,等到状态改变后再执行
        if (this.state === 'pending') {
            this.onFulfilledCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        const result = onFulfilled(this.value);
                        resolve(result);
                    } catch (error) {
                        reject(error);
                    }
                });
            });

            this.onRejectedCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        const result = onRejected(this.reason);
                        resolve(result);
                    } catch (error) {
                        reject(error);
                    }
                });
            });
        }
    });
}
  • 链式调用: then 方法需要返回一个新的 MyPromise 实例,以便支持链式调用。

  • 回调处理: 当状态为 fulfilledrejected 时,立即执行对应的回调,并将 resolvereject 的结果传递给下一个 then

  • 异步执行: 使用 setTimeout 模拟异步执行,确保回调函数是在 resolvereject 之后才执行。

  • pending 状态处理: 当状态还是 pending 时,先将回调函数存储起来,等状态改变后再执行。

实现 catch 方法

catch 方法是 then 方法的一个变种,它只关注失败的情况。我们可以直接使用 then 方法来实现 catch

js 复制代码
catch(onRejected) {
    return this.then(null, onRejected);
}

catch 方法只处理 rejected 状态,因此它只传入 onRejected 回调,将 onFulfilled 设置为 null

Promise.all

Promise.all 是一个静态方法,接收一个可迭代对象(如数组),这个对象的每个元素都是 Promise。它的主要作用是:

并行执行多个 Promise。当所有 Promise 都完成(即全部解决)时,返回一个新的 Promise,这个新的 Promise 的结果是一个包含每个 Promise 结果的数组,顺序与传入的数组顺序一致。 如果其中任何一个 Promise 被拒绝,则 Promise.all 立即返回被拒绝的 Promise,并且不会等待其他 Promise 的完成。

使用示例:

js 复制代码
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values); // 输出: [3, 42, "foo"]
  });

Promise.all 与其他 Promise 方法的区别

Promise API 中,除了 Promise.all 之外,还有几个常用的静态方法,例如 Promise.racePromise.anyPromise.allSettled。它们的主要区别如下:

  1. Promise.all :等到所有 Promise 都成功才返回结果,如果其中任何一个 Promise 被拒绝,则整个操作失败。
  2. Promise.race :只要数组中任何一个 Promise 首先完成(无论是解决还是拒绝),Promise.race 就会返回该 Promise 的结果。
  3. Promise.any :只要数组中任何一个 Promise 首先解决,Promise.any 就会返回该解决值。只有当所有 Promise 都被拒绝时,它才会返回拒绝。
  4. Promise.allSettled :等待所有 Promise 都解决或拒绝,返回的结果数组包含每个 Promise 的状态和结果,无论成功或失败。

手动实现Promise.all

现在我们来实现 Promise.all 的核心逻辑,帮助我们深入理解其工作原理。Promise.all 需要处理以下几种情况:

  • 接收的参数是一个可迭代对象(如数组)。
  • 确保传入的每个元素都是 Promise 实例。
  • 等待所有 Promise 都解决时,返回一个包含所有结果的数组。
  • 如果其中任何一个 Promise 被拒绝,立即返回拒绝。
js 复制代码
function myPromiseAll(promises) {
    return new Promise((resolve, reject) => {
        // 检查传入参数是否为数组
        if (!Array.isArray(promises)) {
            return reject(new TypeError('参数必须是数组'));
        }

        // 用于保存每个 Promise 的结果
        let results = [];
        // 记录已完成的 Promise 数量
        let computed = 0;
        // 总的 Promise 数量
        const total = promises.length;

        // 如果传入的数组为空,直接返回已解析的 Promise
        if (total === 0) {
            return resolve(results);
        }

        // 遍历每一个传入的 Promise
        promises.forEach((promise, index) => {
            // 使用 Promise.resolve 确保即使传入的不是 Promise 也能处理
            Promise.resolve(promise)
                .then(value => {
                    // 将每个 Promise 结果保存到对应的索引中
                    results[index] = value;
                    computed++;

                    // 当所有 Promise 都已解决,返回最终结果
                    if (computed === total) {
                        resolve(results);
                    }
                })
                .catch(error => {
                    // 如果有任何一个 Promise 被拒绝,立即返回失败的 Promise
                    reject(error);
                });
        });
    });
}

总结

到这里,我们已经实现了一个简单版的 Promise。通过自定义 MyPromise,我们了解了 Promise 的核心原理以及如何处理异步逻辑、状态管理和回调机制。希望这篇文章能够帮助大家更好地理解 Promise,在面试中轻松应对相关问题。如果这篇文章对你有帮助可以点个赞哦😊!

相关推荐
_AaronWong20 小时前
Electron 实现仿豆包划词取词功能:从 AI 生成到落地踩坑记
前端·javascript·vue.js
cxxcode20 小时前
I/O 多路复用:从浏览器到 Linux 内核
前端
用户54330814419420 小时前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo20 小时前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
JohnYan20 小时前
工作笔记-CodeBuddy应用探索
javascript·ai编程·aiops
恋猫de小郭20 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木20 小时前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮20 小时前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati20 小时前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉21 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain