源码解读——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,在面试中轻松应对相关问题。如果这篇文章对你有帮助可以点个赞哦😊!

相关推荐
m0_748247551 小时前
Web 应用项目开发全流程解析与实战经验分享
开发语言·前端·php
m0_748255022 小时前
前端常用算法集合
前端·算法
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
web130933203982 小时前
vue elementUI form组件动态添加el-form-item并且动态添加rules必填项校验方法
前端·vue.js·elementui
NiNg_1_2342 小时前
Echarts连接数据库,实时绘制图表详解
前端·数据库·echarts
测试老哥3 小时前
外包干了两年,技术退步明显。。。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
如若1233 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~4 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语4 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport4 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap