手写简化版 Promise 详解

手写简化版 Promise 详解

在JavaScript中,Promise 是一种用于处理异步操作的强大机制。虽然现代JavaScript环境(如Node.js和浏览器)已经内置了功能完备的 Promise 实现,但了解如何手写一个简化版的 Promise 可以帮助我们深入理解其内部工作原理。

一、Promise 的基本结构

一个基本的 Promise 实现需要包含几个关键部分:状态管理、执行器(executor)函数、以及处理成功和失败的回调函数的队列。

1. 状态管理

Promise 有三种状态:pending(等待中)、fulfilled(已成功)、rejected(已失败)。状态一旦改变就不能再变。

class MyPromise {
    constructor(executor) {
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];

        try {
            executor(this._resolve.bind(this), this._reject.bind(this));
        } catch (error) {
            this._reject(error);
        }
    }

    _resolve(value) {
        if (this.status === 'pending') {
            this.status = 'fulfilled';
            this.value = value;
            this.onFulfilledCallbacks.forEach(fn => fn(value));
        }
    }

    _reject(reason) {
        if (this.status === 'pending') {
            this.status = 'rejected';
            this.reason = reason;
            this.onRejectedCallbacks.forEach(fn => fn(reason));
        }
    }
}
2. 执行器函数

执行器函数是传递给 Promise 构造函数的参数,它接受两个函数作为参数:resolvereject。这两个函数用于改变 Promise 的状态。

二、then 方法

then 方法是 Promise 的核心,它允许我们为 Promise 成功或失败时注册回调函数。

MyPromise.prototype.then = function(onFulfilled, onRejected{
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

    if (this.status === 'fulfilled') {
        setTimeout(() => onFulfilled(this.value));
    } else if (this.status === 'rejected') {
        setTimeout(() => onRejected(this.reason));
    } else {
        this.onFulfilledCallbacks.push(onFulfilled);
        this.onRejectedCallbacks.push(onRejected);
    }

    // 链式调用和错误传递的实现(简化版)
    return new MyPromise((resolve, reject) => {
        if (this.status === 'fulfilled') {
            setTimeout(() => {
                try {
                    const result = onFulfilled(this.value);
                    // 这里简化处理,假设result不是Promise
                    resolve(result);
                } catch (error) {
                    reject(error);
                }
            });
        } else if (this.status === 'rejected') {
            // 类似地处理rejected状态
        }
    });
};

注意:上面的 then 方法实现是简化的,它只处理了 fulfilled 状态下的情况,并且没有处理 onFulfilledonRejected 返回 Promise 的情况。在完整的实现中,你需要添加对这些情况的处理。

三、静态方法和链式调用

为了支持 Promise 链式调用和错误传递,then 方法需要返回一个新的 Promise。此外,Promise 还有一些静态方法,如 Promise.allPromise.race,这些方法的实现需要额外的逻辑来跟踪和管理多个 Promise 的状态。

四、优化与完整实现

在完整的 Promise 实现中,你需要考虑更多的边界情况和优化。例如:

  • 处理 then 方法中 onFulfilledonRejected 抛出异常的情况。
  • 支持 then 方法返回 Promise 的情况,这通常通过递归调用 then 方法来实现。
  • 实现 Promise.allPromise.race 等静态方法。
  • 优化性能,例如通过减少不必要的回调函数调用次数。

手写一个完整的 Promise 实现是一个复杂但非常有教育意义的过程,因为它涉及到异步编程、状态管理、以及错误处理等多个方面。下面是一个更加完整但简化的 Promise 实现示例,这个实现将包括基本的状态管理、then 方法(支持返回 Promise 的情况)、以及链式调用和错误传递的支持。

class MyPromise {
    constructor(executor) {
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.status === 'pending') {
                this.status = 'fulfilled';
                this.value = value;
                this.onFulfilledCallbacks.forEach(fn => {
                    this._handleNextPromise(fn, value);
                });
            }
        };

        const reject = (reason) => {
            if (this.status === 'pending') {
                this.status = 'rejected';
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => {
                    this._handleNextPromise(fn, reason);
                });
            }
        };

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    _handleNextPromise(callback, valueOrReason) {
        let returnValue;
        try {
            returnValue = callback(valueOrReason);
        } catch (error) {
            return this._rejectPromise(error);
        }

        if (returnValue instanceof MyPromise) {
            returnValue.then(
                this._resolvePromise.bind(this),
                this._rejectPromise.bind(this)
            );
        } else {
            this._resolvePromise(returnValue);
        }
    }

    _resolvePromise(value) {
        // 在这里简化处理,不考虑循环引用等情况
        this._resolve(value);
    }

    _rejectPromise(reason) {
        this._reject(reason);
    }

    _resolve(value) {
        // 简化处理,不再重复
    }

    _reject(reason) {
        // 简化处理,不再重复
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

        return new MyPromise((resolve, reject) => {
            if (this.status === 'fulfilled') {
                setTimeout(() => {
                    try {
                        this._handleNextPromise(onFulfilled, this.value);
                    } catch (error) {
                        reject(error);
                    }
                });
            } else if (this.status === 'rejected') {
                setTimeout(() => {
                    try {
                        this._handleNextPromise(onRejected, this.reason);
                    } catch (error) {
                        reject(error);
                    }
                });
            } else {
                this.onFulfilledCallbacks.push(onFulfilled);
                this.onRejectedCallbacks.push(onRejected);
            }
        });
    }

    // 这里可以添加更多方法,如 catch, finally, static 方法等
}


new MyPromise((resolve, reject) => {
    setTimeout(() => resolve(42), 1000);
}).then(value => {
    console.log(value); // 输出 42
    return new MyPromise((resolve, reject) => {
        setTimeout(() => resolve(value * 2), 500);
    });
}).then(value => {
    console.log(value); // 输出 84
});

注意

  1. 这个实现仍然有许多简化和未处理的情况,比如循环引用的检测、 Promise 规范化(即处理 then 方法返回的不是 Promise 的情况)等。
  2. _handleNextPromise 方法中,我们尝试执行回调函数,并根据返回值进行进一步的处理。如果回调函数返回了一个 MyPromise 实例,我们将该实例的 then 方法与内部的 resolvereject 方法绑定,以实现链式调用。
  3. setTimeout 的使用是为了模拟异步操作,它确保 then 方法中的回调函数在下一个事件循环中执行,从而模拟真实的异步行为。

在实际应用中,Promise 的实现会更加复杂,特别是要处理各种边界情况和优化性能。不过,基于我们之前的简化实现,我们可以继续添加一些重要的功能,比如 catch 方法和 finally 方法,以及处理 Promise 静态方法如 Promise.allPromise.racePromise.resolvePromise.reject

下面是一个扩展了 catchfinally 方法的 MyPromise 实现:

class MyPromise {
    // ... (之前的代码保持不变)

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

    finally(onFinally) {
        return this.then(
            value => MyPromise.resolve(onFinally()).then(() => value),
            reason => MyPromise.reject(onFinally()).then(() => { throw reason; })
        );
    }

    // 静态方法
    static resolve(value) {
        return new MyPromise(resolve => resolve(value));
    }

    static reject(reason) {
        return new MyPromise((_, reject) => reject(reason));
    }

    // 注意:这里不实现 Promise.all, Promise.race 等,因为它们需要额外的逻辑来处理多个 Promise
}

// 使用示例
MyPromise.resolve(1)
    .then(x => x + 1)
    .then(x => {
        if (x < 5) {
            throw new Error('x is not greater than or equal to 5');
        }
        return x;
    })
    .catch(err => console.error('Caught an error:', err))
    .finally(() => console.log('Finally block executed'));

// 输出:
// Caught an error: Error: x is not greater than or equal to 5
// Finally block executed

在这个扩展中,catch 方法简单地调用了 then 方法,并传递了 null 作为第一个参数(即不处理 fulfilled 状态),只传递了错误处理函数作为第二个参数。

finally 方法稍微复杂一些,它接受一个回调函数 onFinally,该函数会在 Promise 结束时(无论是 fulfilled 还是 rejected)执行。finally 方法需要确保原始 Promise 的结果(值或错误)被正确传递。这里,我们使用了 MyPromise.resolve(onFinally()).then(...) 来确保 onFinally 函数被异步执行,并且其结果不会影响原始 Promise 的值或错误。如果 onFinally 函数返回了一个 Promise,我们需要等待它解决后再继续传递原始 Promise 的结果。

请注意,这里的 MyPromise.resolve(onFinally()).then(...) 假设 onFinally 函数不会抛出错误,或者即使它抛出错误,我们也希望忽略它并继续执行原始 Promise 的后续处理。在实际应用中,你可能需要添加额外的错误处理逻辑来确保 finally 块中的错误被适当地捕获和处理。

最后,MyPromise.resolveMyPromise.reject 是静态方法,用于快速创建已解决或已拒绝的 Promise 对象。这些方法在实际应用中非常有用,因为它们允许你以编程方式创建 Promise 对象,而无需定义 executor 函数。

当然,我们可以继续扩展MyPromise类,以下是一个简单的Promise.allPromise.race的模拟实现,添加到我们的MyPromise类中:

class MyPromise {
    // ... (之前的代码保持不变,包括then, catch, finally, resolve, reject)

    // 静态方法 Promise.all
    static all(promises) {
        return new MyPromise((resolve, reject) => {
            let results = [];
            let remaining = promises.length;

            if (remaining === 0) {
                resolve(results);
                return;
            }

            promises.forEach((p, index) => {
                MyPromise.resolve(p).then(
                    value => {
                        results[index] = value;
                        if (--remaining === 0) {
                            resolve(results);
                        }
                    },
                    err => {
                        reject(err);
                    }
                );
            });
        });
    }

    // 静态方法 Promise.race
    static race(promises) {
        return new MyPromise((resolve, reject) => {
            promises.forEach(p => {
                MyPromise.resolve(p).then(resolve, reject);
            });
        });
    }

    // ... (其他代码)
}

// 使用示例
MyPromise.all([
    MyPromise.resolve(1),
    MyPromise.resolve(2).then(x => x * 2),
    MyPromise.reject(new Error('Failed')),
    MyPromise.resolve(4)
]).then(values => console.log(values))
.catch(err => console.error('Failed:', err));

// 输出: Failed: Error: Failed

MyPromise.race([
    MyPromise.resolve(1).delay(5000), // 注意:这里假设delay是一个自定义扩展,实际Promise没有
    MyPromise.resolve(2)
]).then(value => console.log(value));

// 输出: 2

// 注意:由于JavaScript原生的Promise没有delay方法,这里只是一个示例。
// 在实际使用中,你可能需要使用setTimeout或其他方式来实现延迟。

注意:上面的delay方法并不是Promise原生的一部分,我只是用它来模拟一个长时间运行的Promise

MyPromise.all方法等待所有给定的Promise都成功解决,并将它们的值作为一个数组返回。如果任何一个Promise被拒绝,那么MyPromise.all立即以相同的错误拒绝。

MyPromise.race方法返回一个新的Promise,该Promise在所有给定的Promise中解决得最快的一个解决时解决,其解决值与那个最快的Promise的解决值相同。如果任何一个Promise被拒绝,返回的Promise也立即以相同的错误被拒绝。

这些扩展使得MyPromise类更加强大和灵活,能够处理更复杂的异步逻辑。然而,请注意,为了完全模拟原生Promise的行为,还需要考虑更多的边界情况和优化。

结论:

通过实现MyPromise类及其扩展功能,我们不仅加深了对Promise工作原理的理解,还学会了如何以编程方式创建和管理异步操作。虽然MyPromise类在功能上可能不如原生Promise完整,但它为学习异步编程和Promise模式提供了一个很好的起点。随着对JavaScript和异步编程的进一步学习,你将能够更深入地理解和应用这些概念。

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/50760.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue3+Element Plus 实现table表格中input的验证

实现效果 html部分 <template><div class"table"><el-form ref"tableFormRef" :model"form"><el-table :data"form.detailList"><el-table-column type"selection" width"55" align&…

基于springboot+vue+uniapp的养老院系统小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

repo 工具安装和使用教程(windows+gitee)

repo是什么 官方的定义&#xff1a;Repo是谷歌用python脚本写的调用git的一个脚本&#xff0c;可以实现管理多个git库。 Android的源代码使用Repo 命令行工具来管理多个git仓库&#xff0c;大概有百多个。要想克隆和管理百多个 Git 仓库&#xff0c;不是一件简单的事情。Repo 命…

LoRaWAN网络中的chirpstack

目录 一、chirpstack介绍 二、网关与chirpstack之间的通信 三、NS与AS之间的通信 1、Protobuf 2、gRPC 一、chirpstack介绍 ChirpStack 是一个开源的 LoRaWAN 网络服务器&#xff0c;可用于 设置私有或公共 LoRaWAN 网络。ChirpStack 提供了一个 Web 界面 用于管理网关、设…

HBuilder X中配置vue-cli项目和UI库

目录 一.前端项目结构 二.在HBuilder X中搭建vue-cli项目 1. 安装node.js前端环境 2. HBuilder X创建一个vue-cli项目 3. vue-cli项目结构 4. 如何运行前端项目 5. 创建组件 6. 组件路由(页面跳转) 6.1 创建router目录 6.2 使用路由 6.3 在main.js中配置路由 6.4 路…

【IoTDB 线上小课 05】时序数据文件 TsFile 三问“解密”!

【IoTDB 视频小课】持续更新&#xff01;第五期来啦~ 关于 IoTDB&#xff0c;关于物联网&#xff0c;关于时序数据库&#xff0c;关于开源... 一个问题重点&#xff0c;3-5 分钟详细展开&#xff0c;为大家清晰解惑&#xff1a; IoTDB 的 TsFile 科普&#xff01; 了解了时序数…

安卓adb shell top 命令的使用

adb shell top 是一个在 Android 开发中常用的命令&#xff0c;它使用 Android Debug Bridge (adb) 来运行 top 命令&#xff0c;这通常用于监视 Android 设备上的进程和系统资源使用情况。 当你在命令行中输入 adb shell top 并按下回车键时&#xff0c;它会显示一个动态更新…

系统移植(三)u-boot移植 ① 相关概念

文章目录 一、u-boot概念&#xff08;一&#xff09;概念&#xff08;二&#xff09;获取u-boot源码1.从u-boot官网获取2. 从 STM官网3. 开发板厂商获取 &#xff08;三&#xff09;分析u-boot源码1. u-boot源码的目录结构2. 获取make的帮助信息3. 分析README文件 &#xff08;…

Spark核心知识要点(二)

1、Spark有哪两种算子&#xff1f; Transformation&#xff08;转化&#xff09;算子和Action&#xff08;执行&#xff09;算子。 2、Spark有哪些聚合类的算子,我们应该尽量避免什么类型的算子&#xff1f; 在我们的开发过程中&#xff0c;能避免则尽可能避免使用reduceByK…

讨逆猴子剪切板,浏览器复制失败?

讨逆猴子剪切板&#xff0c;复制失败&#xff1f; 问题&#xff1a;本地开发情况下可以直接复制&#xff0c;公网就不行了…触发了安全机制。 const link 内容;navigator.clipboard.writeText(link);报错&#xff1a; 解决方案&#xff1a; if (navigator.clipboard &&…

使用代理IP进行本地SEO优化:如何吸引附近的客户?

在今天竞争激烈的互联网时代&#xff0c;如何利用代理IP进行本地SEO优化并吸引附近的客户已经成为许多企业和网站面临的关键挑战。本文将探讨使用代理IP的策略和技巧&#xff0c;以帮助公司提高在本地市场的可见性和吸引力&#xff0c;从而扩大本地客户群体。 1. 代理IP在本地…

java算法实现-1

1. 算法编程 1&#xff1a;请编写Java代码实现实现以下逻辑与输出 &#xff1f; 题目&#xff1a;古典问题&#xff1a;有一对兔子&#xff0c;从出生后第3个月起每个月都生一对兔子&#xff0c;小兔子长到第四个月后每个月又生一对兔子&#xff0c;假如兔子都不死&#xff0c…

JAVA通过实体类注解生成测试数据

注解 package cn.ac.iscas.utils;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/*** 注解用于生成测试数据*/ Retention(RetentionPolicy.RUNTIME) T…

SpringBoot 调用外部接口的三种方式

1. 简介 SpringBoot 简化了Spring应用的搭建和开发&#xff0c;支持访问外部模块接口或URL。需求场景 如apaas开发中封装接口调用外部服务。 2. 方式一&#xff1a;使用原始httpClient请求 实现 通过get方式获取参数&#xff0c;发起流程。关键代码 将数据转换为JSON格式。使…

Windosw下Visual Studio2022编译FFmpeg(支持x264、x265、fdk-acc)

FFmpeg 7.0 版本移除了 6.0 之前已弃用的 API&#xff0c;无法向下兼容。所以编译的版本选择FFmpeg 6.1.1。 一、安装Visual Studio2022 可参考另外一篇文章&#xff1a;Windows安装Visual Studio2022 QT5.15开发环境_qt5.15.2 vs2022-CSDN博客 二、安装MSYS2 下载地址&…

lua 游戏架构 之 游戏 AI (四)ai_autofight_find_target

定义一个名为 ai_autofight_find_target 的类&#xff0c;继承自 ai_base 类。 lua 游戏架构 之 游戏 AI &#xff08;一&#xff09;ai_base-CSDN博客文章浏览阅读237次。定义了一套接口和属性&#xff0c;可以基于这个基础类派生出具有特定行为的AI组件。例如&#xff0c;可…

nfs、web与dns结合练习

1.搭建一个nfs服务器&#xff0c;客户端可以从该服务器的/share目录上传并下载文件 #服务端 1. 下载rpcbind和nfs-utils [root128 ~]# yum install rpcbind [root128 ~]# yum install nfs-utils2. 创建共享目录 [root128 ~]# mkdir /share3.编辑配置 [root128 ~]# vim /etc/ex…

大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Docker+consul容器服务的更新与发现

1、Consul概述 &#xff08;1&#xff09;什么是服务注册与发现 服务注册与发现是微服务架构中不可或缺的重要组件。起初服务都是单节点的&#xff0c;不保障高可用性&#xff0c;也不考虑服务的压力承载&#xff0c;服务之间调用单纯的通过接口访问。直到后来出现了多个节点…

Spark实时(三):Structured Streaming入门案例

文章目录 Structured Streaming入门案例 一、Scala代码如下 二、Java 代码如下 三、以上代码注意点如下 Structured Streaming入门案例 我们使用Structured Streaming来监控socket数据统计WordCount。这里我们使用Spark版本为3.4.3版本&#xff0c;首先在Maven pom文件中导…