JavaScript进阶之手写Promise

article/2025/11/11 15:38:55

前言


Promise异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。这里手写一次,希望能和大家一起彻底掌握Promise。

概述


所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

Promise对象有以下两个特点:

  1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

上图摘自MDN

Promise的具体用法可以参考阮一峰老师的 《ECMAScript 6 入门》或MDN

这里有几点需要注意:

  1. Promise 新建之后就会立即执行
  2. Promise 状态已经变成resolved,再抛出错误是无效的。
  3. 一般来说,调用resolvereject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolvereject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
  4. 建议省略then中的第二个参数,并在then之后使用catch捕获异常,这是因为这样写不但可以捕获之前的异常,还可以捕获当前then抛出的异常

开始


有了上面的简介及promise的特征,我们来一步步实现Promise,为了和es6的Promise区分,我们使用JPromise来做类名。

  1. 构造函数只有一个executor函数最为参数,并且立即执行了 ,executor函数有两个回调函数:resolve和reject
    class JPromise{constructor(executor){function resolve(value){}function reject(error){}try {executor(resolve,reject);} catch (error) {console.log(error);}}}

     2. 定义promise的三种状态,执行resolve时,状态由pending变为fulfill,执行reject时,状态由pending变为rejecting。then中根据当前实例的状态执行对应的方法。

    /* * 定义状态常量*/const PENDING = 1;const FULFILLED = 2;const REJECTING = 3;/* * 定义JPromise构造函数,定义then原型方法* @params {Function} excutor;函数参数依次是resolve和reject* * @return {Object};返回promise实例对象*/class JPromise{constructor(excutor){const me = this;me.status = PENDING;me.value = null;me.error = null;function resolve(val){if(me.status === PENDING){//检查状态,不可逆操作me.status = FULFILLED;me.value = val;}}function reject(val){if(me.status === PENDING){//检查状态,不可逆操作me.status = REJECTING;me.error = val;}}try {excutor(resolve,reject);} catch (error) {reject(error);}}then(onResolve,onReject){const me = this;if(me.status === FULFILLED){onResolve(me.value);}if(me.status === REJECTING){onReject(me.error);}}}

执行测试代码:

    new JPromise((resolve)=>{resolve(1);}).then((res)=>{console.log(res);});

返回结果:

     3. 如果我们的resolve或者reject回调函数中有异步操作,那么上面的代码就不会返回我们预期的结果了,这时,我们要开始添加异步处理:

第一步:我们先定义两个实例数组属性(resolveCallback和rejectCallback)用来存储then的两个回调函数(使用数组是应为一个promise实例可能使用一次或者多次then方法)

第二步:在调用then方法的时候,如果状态还未改变,我们就向resolveCallback、rejectCallback数组中增加then参数对应的方法;如果状态已经改变,我们就直接使用当前结果传入then方法的参数方法中并执行。

第三步:Promise中的resolve或reject函数执行时,触发resolveCallback或者rejectCallback中的函数。就实现异步调用then中的方法了。

    /* * 同步JPromise的功能添加异步执行功能*//* * 定义状态常量*/const PENDING = 1;const FULFILLED = 2;const REJECTING = 3;class JPromise{constructor(excutor){const me = this;me.status = PENDING;me.value = null;me.error = null;me.resolveCallback = [];//以数组存储多个then的回调函数me.rejectCallback = [];//以数组存储多个then的回调函数function resolve(val){if(me.status === PENDING){//检查状态,不可逆操作me.status = FULFILLED;me.value = val;me.resolveCallback.forEach(func => func(val));//状态改变为fulfilled并执行resolve}}function reject(val){if(me.status === PENDING){//检查状态,不可逆操作me.status = REJECTING;me.error = val;me.rejectCallback.forEach(func => func(val));//状态改变为rejecting并执行reject}}try {excutor(resolve,reject);} catch (error) {reject(error);}}then(onResolve,onReject){const me = this;onResolve = typeof onResolve === 'function'? onResolve: v => v;onReject = typeof onReject === 'function'? onReject: e => {throw e;}if(me.status === PENDING){//状态未改变,存储回调不执行me.resolveCallback.push(onResolve);me.rejectCallback.push(onReject);}else if(me.status === FULFILLED){setTimeout(() => {//保证then是异步执行的try {onResolve(me.value);} catch (error) {onReject(error);}});}else if(me.status === REJECTING){setTimeout(() => {//保证then是异步执行的try {onReject(me.error);} catch (error) {onReject(error);}});}}}

执行测试代码:

    new JPromise((resolve,reject) => {console.log('before resolve');setTimeout(() => {resolve('resolved');},1000);console.log('after resolve');}).then(res =>{console.log(res);});//运行结果://before resolve//after resolve//resolved (延迟1秒后运行)var p = new JPromise((resolve,reject) => {setTimeout(() => {resolve('delay 2000s');p.then(res => {console.log(`run after resolve,res is:  ${res}`);});},2000);});p.then(res => {console.log(res);});p.then(res => {console.log(`${res} run again`);});//运行结果://2秒后://delay 2000s//delay 2000s run again//run after resolve,res is:  delay 2000s

执行结果和预期的一样。

     4. 实现then的链式操作,resolve、reject可传入promise实例作为参数

在then中返回一个新的promise,如果状态还未改变,则在改该promise的构造函数中执行存储回调函数;否则立即执行回调函数。catch和finally函数都是then函数的调用

    /* * 定义状态常量*/const PENDING = 1;const FULFILLED = 2;const REJECTING = 3;class JPromise{constructor(excutor){const me = this;me.status = PENDING;me.value = null;me.error = null;me.resolveCallback = [];//以数组存储多个then的回调函数me.rejectCallback = [];//以数组存储多个then的回调函数function resolve(val){if(val instanceof JPromise){//resolve传入promise对象return val.then(resolve,reject);}if(me.status === PENDING){//检查状态,不可逆操作me.status = FULFILLED;me.value = val;me.resolveCallback.forEach(func => func(val));//状态改变为fulfilled并执行resolve}}function reject(val){if(val instanceof JPromise){//resolve传入promise对象return val.then(resolve,reject);}if(me.status === PENDING){//检查状态,不可逆操作me.status = REJECTING;me.error = val;me.rejectCallback.forEach(func => func(val));//状态改变为rejecting并执行reject}}try {excutor(resolve,reject);} catch (error) {reject(error);}}/* * 链式操作不是返回this,而是返回一个新的promise实例*/then(onResolve,onReject){const me = this;onResolve = typeof onResolve === 'function'? onResolve: v => v;onReject = typeof onReject === 'function'? onReject: e => {throw e;}if(me.status === PENDING){return new JPromise((resolve,reject) => {//返回新的实例me.resolveCallback.push(() => {try {resolvePromise(onResolve(me.value),resolve,reject);} catch (error) {reject(error);}});me.rejectCallback.push(() => {try {resolvePromise(onReject(me.error),resolve,reject);} catch (error) {reject(error);}});});}else if(me.status === FULFILLED){return new JPromise((resolve,reject) => {//返回新的实例setTimeout(() => {try {resolvePromise(onResolve(me.value),resolve,reject);} catch (error) {reject(error);}});});}else if(me.status === REJECTING){return new JPromise((resolve,reject) => {//返回新的实例setTimeout(() => {try {onReject(me.error);} catch (error) {reject(error);}});});}}/* * 捕获promise抛出错误或者reject状态改变返回值*/catch(onReject){return this.then(null,onReject);}/* * 总是最后执行*/finally(callback){return this.then(callback,callback);}}function resolvePromise(retValue,resolve,reject){if(retValue instanceof JPromise){//resolve或reject传入promise实例if(retValue.status === PENDING){retValue.then(ret => {resolvePromise(ret,resolve,reject);},error => {reject(error);});}else{retValue.then(resolve,reject);}}else{resolve(retValue);}}

执行测试代码:

    var p = new JPromise((resolve,reject) => {setTimeout(() => {resolve('1000ms delay--p');},1000);}).then(res => {console.log(res);return new JPromise((resolve,reject) => {setTimeout(() => {console.log('run after 2000ms--p');resolve('2000ms delay--p');},1000);});}).then(res => {console.log(res);});//运行结果://1000ms delay--p (1s 后执行)//run after 2000ms--p (2s 后执行)//2000ms delay--p (2s 后执行)var p1 = new JPromise((resolve,reject) => {setTimeout(() => {console.log('1000ms delay--p1');return resolve(new JPromise((rs,rj) => {setTimeout(() => {return rs('delay 2000ms--p1');},1000);}));},1000);}).then(res => {console.log(res);});//运行结果://1000ms delay--p1 (1s后执行)//delay 2000ms--p1 (2s 后执行)var p2 = new JPromise((resolve,reject) => {reject(1);}).then(res => {console.log(res);throw new Error('error');},err => {console.log('then catch error');throw new Error('inner error');}).catch(err => {console.log(err);});//运行结果://then catch error//inner errorvar p3 = new JPromise((resolve,reject) => {setTimeout(() => {reject(1);},1000);}).catch(err => {console.log(err); return new JPromise((rs,rj) => {setTimeout(() => {rs(2);},1000);});}).then(res => {console.log(res); return 'foo';}).finally(s => {console.log(s); console.log('finally run');});//运行结果://1 (1s后)//2 (2s后)//foo (2s后)//finally run (2s后)

执行结果:

     5. 实现全局方法all和race

all是传入一组promise实例,直至所有实例状态都变为fulfill状态则执行then中的resolve,参数为所有实例resolve传入的值组成的数组,否则执行reject;实现原理为为数组中的每一个实例代理添加一个then方法,方法中定义一个计数器,当计数器和实例数组长度相等时,改变返回promise的状态,继续实现链式操作

race接收一组promise实例,当只有某一个实例率先状态改变为fulfill时,执行resolve,参数为该promise的resolve传入的值;改变为reject时执行reject。race的实现就比较简单了,只要数组中的一个实例状态改变,则改变新返回的实例状态。

/* * 定义状态常量*/const PENDING = 1;const FULFILLED = 2;const REJECTING = 3;class JPromise{constructor(excutor){const me = this;me.status = PENDING;me.value = null;me.error = null;me.resolveCallback = [];//以数组存储多个then的回调函数me.rejectCallback = [];//以数组存储多个then的回调函数function resolve(val){if(val instanceof JPromise){//resolve返回promise对象return val.then(resolve,reject);}if(me.status === PENDING){//检查状态,不可逆操作me.status = FULFILLED;me.value = val;me.resolveCallback.forEach(func => func(val));//状态改变为fulfilled并执行resolve}}function reject(val){if(me.status === PENDING){//检查状态,不可逆操作me.status = REJECTING;me.error = val;me.rejectCallback.forEach(func => func(val));//状态改变为rejecting并执行reject}}try {excutor(resolve,reject);} catch (error) {reject(error);}}/* * 链式操作不是返回this,而是返回一个新的promise实例*/then(onResolve,onReject){const me = this;onResolve = typeof onResolve === 'function'? onResolve: v => v;onReject = typeof onReject === 'function'? onReject: e => {throw e;}if(me.status === PENDING){return new JPromise((resolve,reject) => {//返回新的实例me.resolveCallback.push(() => {try {resolvePromise(onResolve(me.value),resolve,reject);} catch (error) {reject(error);}});me.rejectCallback.push(() => {try {resolvePromise(onReject(me.error),resolve,reject);} catch (error) {reject(error);}});});}else if(me.status === FULFILLED){return new JPromise((resolve,reject) => {//返回新的实例setTimeout(() => {try {resolvePromise(onResolve(me.value),resolve,reject);} catch (error) {reject(error);}});});}else if(me.status === REJECTING){return new JPromise((resolve,reject) => {//返回新的实例setTimeout(() => {try {onReject(me.error);} catch (error) {reject(error);}});});}}/* * 捕获promise抛出错误或者reject状态改变返回值*/catch(onReject){return this.then(null,onReject);}/* * 总是最后执行*/finally(callback){return this.then(callback,callback);}static resolve(value){return value instanceof JPromise ? value: new JPromise(resolve => {return resolve(value);});}static reject(error){return value instanceof JPromise ? value: new JPromise(null,reject => {return reject(error);});}static all(promises){promises = Array.isArray(promises) ? promises : [promises];return new JPromise((resolve,reject) => {const length = promises.length;let values = [];//返回结果组成的数组let counter = 0;promises.forEach((singlePromise,index) => {if(!(singlePromise instanceof JPromise)){//如果不是promise实例则调用resolve全局方法singlePromise = JPromise.resolve(singlePromise);}singlePromise.then(ret => {values[index] = ret;//保证返回结果按照传入的数组顺序来if(++counter === length){//所有promise都执行resolve时,返回的promise执行resolveresolve(values);}},reject);});});}static race(promises){promises = Array.isArray(promises) ? promises : [promises];return new JPromise((resolve,reject) => {if(!(singlePromise instanceof JPromise)){//如果不是promise实例则调用resolve全局方法singlePromise = JPromise.resolve(singlePromise);}promises.forEach(singlePromise => {singlePromise.then(resolve,reject);//执行第一个状态改变的promise,并返回该值到新返回的promise的resolve})});}}function resolvePromise(retValue,resolve,reject){if(retValue instanceof JPromise){if(retValue.status === PENDING){retValue.then(ret => {resolvePromise(ret,resolve,reject);},error => {reject(error);});}else{retValue.then(resolve,reject);}}else{resolve(retValue);}}

测试代码:

var p1 = new JPromise((resolve,reject) => {setTimeout(() => {resolve(1);},2000);});var p2 = new JPromise((resolve,reject) => {setTimeout(() => {resolve(2);},1000);});JPromise.all([p1,p2,'aa']).then(res => {console.log(res);});//运行结果://[1,2,'aa'] (2s 后)var p3 = new JPromise((resolve,reject) => {setTimeout(() => {reject('some error')},2000);});var p4 = new JPromise((resolve,reject) => {setTimeout(() => {resolve(1);},1000);});JPromise.all([p3,p4]).then(res => {cosnole.log(res);},error => {console.log('rejected');});//运行结果://rejected (2s 后)var p5 = new JPromise((resolve,reject) => {setTimeout(() => {resolve(1);},2000);});var p6 = new JPromise((resolve,reject) => {setTimeout(() => {resolve(2);},1000);});var p7 = new JPromise((resolve,reject) => {setTimeout(() => {reject('someErrors');},1500);});JPromise.race([p5,p6,p7]).then(res => {console.log(res);},error => {console.log(error);});//运行结果://2 (1s 后)

运行结果:

至此,一个比较规范的promise实现了,申明:以上代码使用es6风格代码,均可改成es5。代码都上传到GitHub

 

参考


《ECMAScript 6 入门》

《ES6 系列之我们来聊聊 Promise 》

《八段代码彻底掌握 Promise》

《Promise原理讲解 && 实现一个Promise对象 》

《Promise》

 


http://chatgpt.dhexx.cn/article/wN3621Ra.shtml

相关文章

【数据库】MongoDB数据库详解

目录 一,数据库管理系统 1, 什么是数据库 2,什么是数据库管理系统 二, NoSQL 是什么 1,NoSQL 简介 2,NoSQL数据库 3,NoSQL 与 RDBMS 对比 三,MongoDB简介 1, MongoDB 是什…

面试06,[长亮科技]()(offer)、[荔枝]()FM(在确定部门和薪资)、[涂鸦智能]()(第一轮电话面半小时,待后续)、华资软件(HR面)、[广州速游]()(已挂)。至于公司怎么样不加以言论。

作者:Carson-Zhao 链接:https://ac.nowcoder.com/discuss/522002?type2&order0&pos16&page1&ncTraceId&channel-1&source_iddiscuss_tag_nctrack 来源:牛客网 总结一下这几天的面试吧!从19号到现在23号…

SQL Foundation(1--13)

1:关系数据库的由来: IBM的工程师Dr E F codd 的关系型数据库模型发表于1970 论文名称: A relational Model of data for Large Shared Data Bank (这个在wiki 和google上可以搜到) SQL: Structured query language: oracle官方…

一、快速入门 MongoDB 数据库

文章目录 一、NoSQL 是什么1.1 NoSQL 简史1.2 NoSQL 的种类及其特性1.3 NoSQL 特点1.4 NoSQL 的优缺点1.5 NoSQL 与 SQL 数据库的比较 二、MongoDB 基础知识2.1 MongoDB 是什么2.2 MongoDB 的体系结构2.3 MongoDB 的特点2.4 MongoDB 键特性2.5 MongoDB 的核心服务和工具2.6 Mon…

数据库总结(考研复试和期末复习皆可用)

数据库总结 点击下载该文档 密码:cqoq 本人自制了简答题的速记卡片 地址,大家可以参考使用。[下载Markji App 使用] 第一章 绪论 1.1 数据库系统概述 数据库管理系统(DBMS)的功能: 数据定义功能数据组织、存储和管理数据库操纵功能数据库的事务和运行…

一步步教你轻松学KNN模型算法

一步步教你轻松学KNN模型算法 ( 白宁超 2018年7月24日08:52:16 ) 导读:机器学习算法中KNN属于比较简单的典型算法,既可以做聚类又可以做分类使用。本文通过一个模拟的实际案例进行讲解。整个流程包括:采集数据、数据格式化处理、数据分析、数…

Oracle实战详解

Oracle实战详解 1.oracle介绍 ORACLE数据库系统是美国ORACLE公司(甲骨文)提供的以分布式数据库为核心的一组软件产品,是目前最流行的客户/服务器(CLIENT/SERVER)或B/S体系结构的数据库之一。比如SilverStream就是基于数据库的一种中间件。ORA…

数据库|SQL / MySQL的基本理论用法

本文从数据库MySQL的数据类型、关系模型、增删改查语句、管理MySQL、实用SQL语句、事务等方面进行介绍。 数据类型 对于一个关系表,除了定义每一列的名称外,还需要定义每一列的数据类型。关系数据库支持的标准数据类型包括数值、字符串、时间等&#xf…

KNN模型算法研究与案例分析

KNN模型算法研究与案例分析( 白宁超 2018年8月29日15:39:13 ) 导读:机器学习算法中KNN属于比较简单的典型算法,既可以做聚类又可以做分类使用。本文通过一个模拟的实际案例进行讲解。整个流程包括:采集数据、数据格式化处理、数据分析、数据归…

SQL总结

目录 简介 在Android中存储数据有时会用到数据库,Android给我们提供了 一系列的API来操作数据库,非常简单,我们只需要输入对应的SQL语句,甚至不懂SQL语句,只传入对应的参数即可使用。还有一些第三方库,如G…

Windows开机启动项设置详解

一、开机启动原理 Windows系统都有一个“启动”文件夹,把需要打开的程序的快捷方式或脚本放到“启动”文件夹里,就可以实现开机自启动。 启动”文件夹分为两种:“系统启动文件夹”和“用户启动文件夹”。 系统启动文件夹 Win10系统“启动”…

「C#」设置开机启动

自己写了个监控键盘按键的小程序。 在界面上实时显示按下的键,但是想实现程序的开机自启如何实现呢。 开机自启动一种是在windows的“C:\Users\用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup”家快捷方式。但是这种方法自测有时候不成功。…

centos7 设置开机启动项

高端的废话就是没有引言这种废话。 1.这里我已我的centos7为例输入: systemctl list-unit-files #查看开机启动表如图下: 最左边就是服务 ,最右边就是状态 。如当你想要服务器开机启动firewalld(防火墙)输入 system…

计算机怎么管理自启,电脑如何设置开机启动项

大家都知道将程序添加到开机启动项进入系统就可以自动打开了,但是有些流氓软件会强制进入开机启动项,这就导致电脑开机速度变慢,内存占用过多,运行卡顿的问题。下面,我就教大家如何设置开机启动 现在几乎家家户户都配备…

电脑设置开机

我们知道电脑可以通过修改系统任务计划来实现定时关机的功能,那么,能不能让电脑实现自动开机的功能呢?答案是可以的,我们可以通过BIOS设置,指定电脑在某个时间点自动开机,接下来,小编来介绍一下如何通过BI…

nginx 设置开机自启动

一、下载 在windows下实现开机自启动需要一个开源项目Windows Service Wrapper 来实现。 我用的是这个版本。 下载下来,放在Nginx根目录下 下载下来,放在Nginx根目录下,名字改为start-nginx.exe,再新建一个txt文件&#xff0c…

Win11开机启动项怎么调整,Win11开机启动项怎么设置

Win11开机启动项怎么调整?Win11开机启动项怎么设置?现在很多应用或软件下载安装之后默认都是开机自启的,如果开机自启的软件多了的话难免会导致系统开机速度变慢。最近有使用win11系统的小伙伴就遇到了这个问题,有网友想了解怎么设…

windows设置开机启动程序

1.新建文件,填写路径 echo off cd F:\程序路径\ //后面填写3D所在的路径 F: //程序的个盘符 run.bat把这个文件填写完成后,改个名字,后缀改为bat,并把这个文件放在机房的程序目录下 2.设置开机计划任务 windows 7 在【开始】菜单中,输入【任务计划】&#xf…

计算机软件自启动设置,设置开机启动项,详细教您电脑如何设置开机启动项

身边不少朋友买了电脑都在比拼电脑开机速度,一般新电脑开机启动时间保持在30s左右就已经很不错了,不过不少朋友电脑开机时间需要1分多钟,不少朋友的答案的设置开机启动项目,尽量减少程序开机启动,那么如何优化提升电脑…

Windows 三种开机自启动的设置方式(全面)

一、修改注册表的方式 进入注册表,点击任务栏开始,输入regedit.exe,进入[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run]位置后,新建一个二进制字符类型的项,数值数据位置填入所需开机自启动的应…