当前位置: 首页 > 编程日记 > 正文

实现一个完美符合Promise/A+规范的Promise

原文在我的博客中:原文地址 如果文章对您有帮助,您的star是对我最好的鼓励~

简要介绍:Promise允许我们通过链式调用的方式来解决“回调地狱”的问题,特别是在异步过程中,通过Promise可以保证代码的整洁性和可读性。本文主要解读Promise/A+规范,并在此规范的基础上,自己实现一个Promise.

一、Promise的使用

在了解Promise规范之前,我们知道主流的高版本浏览器已经支持ECMA中的Promise.

创建一个promise实例:

var p=new Promise(function(resolve,reject){setTimeout(function(){resolve("success")},1000);console.log("创建一个新的promise");
})
p.then(function(x){console.log(x)
})//输出:
创建一个新的promise
success
复制代码

上述是一个promise的实例,输出内容为,“创建一个promise”,延迟1000ms后,输出"success"。

从上述的例子可以看出,promise方便处理异步操作。此外promise还可以链式的调用:

var p=new Promise(function(resolve,reject){resolve()});
p.then(...).then(...).then(...)
复制代码

此外Promise除了then方法外,还提供了Promise.resolve、Promise.all、Promise.race等等方法。

二、Promise/A+规范

Promise/A+规范扩展了早期的Promise/A proposal提案,我们来解读一下Promise/A+规范。

1.术语

(1)"promise"是一个对象或者函数,该对象或者函数有一个then方法

(2)"thenable"是一个对象或者函数,用来定义then方法

(3)"value"是promise状态成功时的值

(4)"reason"是promise状态失败时的值

我们明确术语的目的,是为了在自己实现promise时,保持代码的规范性(也可以跳过此小节)

2.要求

(1)一个promise必须有3个状态,pending,fulfilled(resolved),rejected当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态。当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。

promise英文译为承诺,也就是说promise的状态一旦发生改变,就永远是不可逆的。

(2)一个promise必须有一个then方法,then方法接受两个参数:

promise.then(onFulfilled,onRejected)
复制代码

其中onFulfilled方法表示状态从pending——>fulfilled(resolved)时所执行的方法,而onRejected表示状态从pending——>rejected所执行的方法。

(3)为了实现链式调用,then方法必须返回一个promise

promise2=promise1.then(onFulfilled,onRejected)
复制代码

三、实现一个符合Promise/A+规范的Promise

解读了Promise/A+规范之后,下面我们来看如何实现一个Promise, 首先构造一个myPromise函数,关于所有变量和函数名,应该与规范中保持相同。

1.v1.0 初始版本myPromise

function myPromise(constructor){let self=this;self.status="pending" //定义状态改变前的初始状态self.value=undefined;//定义状态为resolved的时候的状态self.reason=undefined;//定义状态为rejected的时候的状态function resolve(value){//两个==="pending",保证了状态的改变是不可逆的if(self.status==="pending"){self.value=value;self.status="resolved";}}function reject(reason){//两个==="pending",保证了状态的改变是不可逆的if(self.status==="pending"){self.reason=reason;self.status="rejected";}}//捕获构造异常try{constructor(resolve,reject);}catch(e){reject(e);}
}
复制代码

同时,需要在myPromise的原型上定义链式调用的then方法:

myPromise.prototype.then=function(onFullfilled,onRejected){let self=this;switch(self.status){case "resolved":onFullfilled(self.value);break;case "rejected":onRejected(self.reason);break;default:       }
}
复制代码

上述就是一个初始版本的myPromise,在myPromise里发生状态改变,然后在相应的then方法里面根据不同的状态可以执行不同的操作。

var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})
//输出1
复制代码

但是这里myPromise无法处理异步的resolve.比如:

var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});p.then(function(x){console.log(x)})
//无输出
复制代码

2.v2.0基于观察模式实现

为了处理异步resolve,我们修改myPromise的定义,用2个数组onFullfilledArray和onRejectedArray来保存异步的方法。在状态发生改变时,一次遍历执行数组中的方法。

function myPromise(constructor){let self=this;self.status="pending" //定义状态改变前的初始状态self.value=undefined;//定义状态为resolved的时候的状态self.reason=undefined;//定义状态为rejected的时候的状态self.onFullfilledArray=[];self.onRejectedArray=[];function resolve(value){if(self.status==="pending"){self.value=value;self.status="resolved";self.onFullfilledArray.forEach(function(f){f(self.value);//如果状态从pending变为resolved,//那么就遍历执行里面的异步方法});}}function reject(reason){if(self.status==="pending"){self.reason=reason;self.status="rejected";self.onRejectedArray.forEach(function(f){f(self.reason);//如果状态从pending变为rejected, //那么就遍历执行里面的异步方法})}}//捕获构造异常try{constructor(resolve,reject);}catch(e){reject(e);}
}
复制代码

对于then方法,状态为pending时,往数组里面添加方法:

myPromise.prototype.then=function(onFullfilled,onRejected){let self=this;switch(self.status){case "pending":self.onFullfilledArray.push(function(){onFullfilled(self.value)});self.onRejectedArray.push(function(){onRejected(self.reason)});case "resolved":onFullfilled(self.value);break;case "rejected":onRejected(self.reason);break;default:       }
}
复制代码

这样,通过两个数组,在状态发生改变之后再开始执行,这样可以处理异步resolve无法调用的问题。这个版本的myPromise就能处理所有的异步,那么这样做就完整了吗?

没有,我们做Promise/A+规范的最大的特点就是链式调用,也就是说then方法返回的应该是一个promise。

3.v3.0then方法实现链式调用

要通过then方法实现链式调用,那么也就是说then方法每次调用需要返回一个primise,同时在返回promise的构造体里面,增加错误处理部分,我们来改造then方法

myPromise.prototype.then=function(onFullfilled,onRejected){let self=this;let promise2;switch(self.status){case "pending":promise2=new myPromise(function(resolve,reject){self.onFullfilledArray.push(function(){try{let temple=onFullfilled(self.value);resolve(temple)}catch(e){reject(e) //error catch}});self.onRejectedArray.push(function(){try{let temple=onRejected(self.reason);reject(temple)}catch(e){reject(e)// error catch}});})case "resolved":promise2=new myPromise(function(resolve,reject){try{let temple=onFullfilled(self.value);//将上次一then里面的方法传递进下一个Promise的状态resolve(temple);}catch(e){reject(e);//error catch}})break;case "rejected":promise2=new myPromise(function(resolve,reject){try{let temple=onRejected(self.reason);//将then里面的方法传递到下一个Promise的状态里resolve(temple);   }catch(e){reject(e);}})break;default:       }return promise2;
}
复制代码

这样通过then方法返回一个promise就可以实现链式的调用:

p.then(function(x){console.log(x)}).then(function(){console.log("链式调用1")}).then(function(){console.log("链式调用2")})
//输出
1
链式调用1
链式调用2
复制代码

这样我们虽然实现了then函数的链式调用,但是还有一个问题,就是在Promise/A+规范中then函数里面的onFullfilled方法和onRejected方法的返回值可以是对象,函数,甚至是另一个promise。

4.v4.0 then函数中的onFullfilled和onRejected方法的返回值问题

特别的为了解决onFullfilled和onRejected方法的返回值可能是一个promise的问题。

(1)首先来看promise中对于onFullfilled函数的返回值的要求

I)如果onFullfilled函数返回的是该promise本身,那么会抛出类型错误

II)如果onFullfilled函数返回的是一个不同的promise,那么执行该promise的then函数,在then函数里将这个promise的状态转移给新的promise III)如果返回的是一个嵌套类型的promsie,那么需要递归。

IV)如果返回的是非promsie的对象或者函数,那么会选择直接将该对象或者函数,给新的promise。

根据上述返回值的要求,我们要重新的定义resolve函数,这里Promise/A+规范里面称为:resolvePromise函数,该函数接受当前的promise、onFullfilled函数或者onRejected函数的返回值、resolve和reject作为参数。

下面我们来看resolvePromise函数的定义:

function resolvePromise(promise,x,resolve,reject){if(promise===x){throw new TypeError("type error")}let isUsed;if(x!==null&&(typeof x==="object"||typeof x==="function")){try{let then=x.then;if(typeof then==="function"){//是一个promise的情况then.call(x,function(y){if(isUsed)return;isUsed=true;resolvePromise(promise,y,resolve,reject);},function(e){if(isUsed)return;isUsed=true;reject(e);})}else{//仅仅是一个函数或者是对象resolve(x)}}catch(e){if(isUsed)return;isUsed=true;reject(e);}}else{//返回的基本类型,直接resolveresolve(x)}
}
复制代码

改变了resolvePromise函数之后,我们在then方法里面的调用也变成了resolvePromise而不是promise。

myPromise.prototype.then=function(onFullfilled,onRejected){let self=this;let promise2;switch(self.status){case "pending":promise2=new myPromise(function(resolve,reject){self.onFullfilledArray.push(function(){setTimeout(function(){try{let temple=onFullfilled(self.value);resolvePromise(temple)}catch(e){reject(e) //error catch}})});self.onRejectedArray.push(function(){setTimeout(function(){try{let temple=onRejected(self.reason);resolvePromise(temple)}catch(e){reject(e)// error catch}})});})case "resolved":promise2=new myPromise(function(resolve,reject){setTimeout(function(){try{let temple=onFullfilled(self.value);//将上次一then里面的方法传递进下一个Promise状态resolvePromise(temple);}catch(e){reject(e);//error catch}})})break;case "rejected":promise2=new myPromise(function(resolve,reject){setTimeout(function(){try{let temple=onRejected(self.reason);//将then里面的方法传递到下一个Promise的状态里resolvePromise(temple);   }catch(e){reject(e);}})})break;default:       }return promise2;
}
复制代码

这样就能处理onFullfilled各种返回值的情况。

var p=new Promise(function(resolve,reject){resolve("初始化promise")})
p.then(function(){return new Promise(function(resolve,reject){resolve("then里面的promise返回值")})}).then(function(x){console.log(x)})//输出
then里面promise的返回值
复制代码

到这里可能有点乱,我们再理一理,首先返回值有两个:

  • then函数的返回值——>返回一个新promise,从而实现链式调用

  • then函数中的onFullfilled和onRejected方法——>返回基本值或者新的promise

这两者其实是有关联的,onFullfilled方法的返回值可以决定then函数的返回值。

四、检测是否完全符合Promise/A+规范

npm install -g promises-aplus-tests 
复制代码

具体用法请看promise test然后

promises-aplus-tests  myPromise.js
复制代码

结果为:

说明我们的实现完全符合Promise/A+规范。

完整代码的地址

https://github.com/forthealllight/promise-achieve

五、最后补充Typescript实现的Promise/A+规范(可以忽略此节)

interface IConstructor{(resolve:IResolve,reject:IReject):void
}
interface IResolve {(x:any):void
}
interface IReject {(x:any):void
}
function myPromise(constructor:IConstructor):void{let self:object=this;self.status="pending";self.value=undefined;//if pending->resolvedself.reason=undefined;//if pending->rejectedself.onFullfilledArray=[];//to deal with async(resolved)self.onRejectedArray=[];//to deal with async(rejeced)let resolve:IResolve;resolve=function(value:any):void{//pending->resolvedif(self.status==="pending"){self.status="resolved";self.value=value;self.onFullfilledArray.forEach(function(f){f(self.value);})}}let reject:IReject;reject=function(reason:any):void{if(self.status==="pending"){self.status="rejected";self.reason=reason;self.onRejectedArray.forEach(function(f){f(self.reason);})}}//According to the definition that the function "constructor" accept two parameters//error catchtry {constructor(resolve,reject);} catch (e) {reject(e);}
}
复制代码

单纯的写个工具函数,用ts还是有点影响可读性。

相关文章:

用递归法计算斐波那契数列的第n项

斐波纳契数列(Fibonacci Sequence)又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、……在数学上,斐波纳契数列以如下被以递归的方法定义:F00,F11,FnF(n-1)F(n-2)&a…

ArrayList的内存泄露

2019独角兽企业重金招聘Python工程师标准>>> 大家先运行下下面这段代码,看看结果 public class MemoryLeak {public static void main(String[] args) throws InterruptedException {new Thread(new Runnable() {Overridepublic void run() {for (int i …

给 Python 初学者推荐的 IDE 哦!

作者 | 黄伟呢来源 | 数据分析与统计学之美总有一些Python初学者,会问到:学习Python,应该用什么Python IDE?了解到他们使用Python做什么之后,我总结了这篇文章。IDE是集成开发环境的缩写,通俗地说&#xff…

django 2.0路由配置变化

urlpatterns变量​​的语法 urlpatterns应该是path()和/或re_path()实例的Python列表。 首先,Django会使用根路由解析模块(root URLconf)来解析路由。 通常,这是ROOT_URLCONF设置的值,但是如果传入的HttpRequest对象具有urlconf属性&#xff…

用ext_skel,实现一个PHP扩展,添加到PHP并调用

http://www.shinrun.com/PHP 一、开始之前 1. 系统环境:FreeBSD 8.22. AP环境:即已经装好的Apache2.2.17、PHP5.3.8环境3. PHP源码:下载稳定版本源码到当前用户的目录,如,下载PHP 5.3.8到/usr/home/abc下。4. 其它要求…

关于第三方IOS的checkBox框架的使用

关于第三方IOS的checkBox框架的使用 这个框架是从github上下载获取的:M13Checkbox。 只是github的源码项目工程比较久远,所以我把代码部分拷贝到XCode 7.1.0新建的项目里。 下面是展示效果 客户端源码使用参考: 1 #import "ViewControll…

20 个 Pandas 数据实战案例,干货多多

作者 | 俊欣来源 | 关于数据分析与可视化今天我们讲一下pandas当中的数据过滤内容,小编之前也写过也一篇相类似的文章,但是是基于文本数据的过滤,大家有兴趣也可以去查阅一下。下面小编会给出大概20个案例来详细说明数据过滤的方法&#xff0…

Python创建和访问字典

>>> dict1 {a:1,b:2,c:3,d:4}>>> print(a的值是:,dict1[a])a的值是: 1>>> dict4 dict(我 快乐, 你 伤悲)SyntaxError: keyword cant be an expression>>> dict4[你] 改变悲伤>>> dict4{我: 快乐, 你: 改变悲伤}>>>…

C语言九阴真经

发现记忆力越来越差,所以干脆搞这么一个东西,就是把C语言的最常用的语法汇编在一起,不断完善。这样以后只要经常把这个回顾一下就可以了。不然去翻书太多了。。。f.h#define Area 1000 struct student{char *last_name;int student_id;char …

听障人士的“有声桥梁”:百度智能云曦灵-AI手语平台发布

在刚刚落幕的冰雪赛事中,百度智能云曦灵为央视新闻打造的AI手语主播正式上岗,她以流畅、专业的手语服务实时传递冰雪运动的激情。然而在日常生活中,听障人士想要方便地获取信息仍面临着众多困难,无障碍窗口稀缺的问题亟待解决。 …

模拟实现: strstr strcpy strlen strcat strcmp memcpy memmove

模拟实现:strstrstrcpystrlenstrcatstrcmpmemcpymemmove1 strstr 字符串中查找子字符串char * my_strstr(const char *dest, const char *src) {const char *ret dest;const char *p dest;const char *q src;assert(dest ! NULL && src ! NULL); while(r…

【Spring Security】五、自定义过滤器

在之前的几篇security教程中,资源和所对应的权限都是在xml中进行配置的,也就在http标签中配置intercept-url,试想要是配置的对象不多,那还好,但是平常实际开发中都往往是非常多的资源和权限对应,而且写在配…

一星期没完成Ansible任务

这个星期,前4天,我在看Nginx,没有深入Ansible。 这几天我有思考做Ansible的哪个方面,现在我用Ansible可以用,但是没有生产环境,我对基础部分热情不是特别大,应该是基础部分大家在弄,…

Python 批量处理 Excel 数据后,导入 SQL Server

作者 | 老表来源 | 简说Python1、前言2、开始动手动脑2.1 拆解明确需求2.2 安装第三方包2.3 读取excel数据2.4 特殊数据数据处理2.5 其他需求2.6 完整调用代码1、前言今天教大家一个需求:有很多Excel,需要批量处理,然后存入不同的数据表中。2…

最经典的计算机网络新书推荐--计算机网络(第5版)Tanenbaum著中文版上市

作者:Tanenbaum是全球最著名的计算机科学家。linux之父Linus当年就是参考Tanenbaum写的MINIX! Tanenbaum《计算机网络(第5版) 》《现代操作系统(第3版) 》《操作系统设计与实现(第3版) 》《分布式系统原理与范型(第2版) 》《计算机组成结构化方法&#x…

elasticsearch简单操作(二)

让我们建立一个员工目录,假设我们刚好在Megacorp工作,这时人力资源部门出于某种目的需要让我们创建一个员工目录,这个目录用于促进人文关怀和用于实时协同工作,所以它有以下不同的需求:1、数据能够包含多个值的标签、数…

苹果放大招?「廉价版」5G iPhone 将揭晓,M2芯片来袭?

整理 | 张洁来源 | CSDN3 月 2 日,苹果公司正式宣布将于北京时间 3 月 9 日凌晨 2 点举办 2022 年的首场活动,主题为“peek performance(高能传送)”。与去年一样,苹果 2022 年的第一场活动将继续以线上的方式进行。活…

PHP 预编译加速: eAccelerator的安装和性能比较

eAccelerator已经是很常用的PHP平台预编译加速的手段了。今天在自己机器上尝试安装了一下,备忘如下: 获得源代码:http://bart.eaccelerator.net/source/编译:需要有autoconf支持,解包后在源程序目录下:/usr…

合并区间(LintCode)

合并区间 给出若干闭合区间,合并所有重叠的部分。 样例 给出的区间列表 > 合并后的区间列表: [ [[1, 3], [1, 6],[2, 6], > [8, 10],[8, 10], [15, 18][15, 18] ] ]挑战 O(…

Kylin集群部署和cube使用

Kylin集群部署和cube使用 安装集群环境节点 Kylin节点模式 Ip 内存 磁盘Node1 All 192.167.71.11 2G 80GNode2 query 192.168.71.12 1.5G 80GNode3 query 192.168.71.13 1.5G 80GKylin工作原理如下: 集群时间同步Ntp服务自行设置安装kylin之前所需要的环境Hadoop-2.…

就是个控制结构,Scala 能有什么新花样呢?

作者 | luanhz来源 | 小数志导读编程语言中最为基础的一个概念是控制结构,几乎任何代码都无时无刻不涉及到,其实也就无外乎3种:顺序、分支和循环。本文就来介绍Scala中控制结构,主要是分支和循环。Scala中的控制结构实质上与其他编…

快速开发一个PHP扩展

快速开发一个PHP扩展 作者:heiyeluren时间:2008-12-5博客:http://blog.csdn.net/heiyeshuwu 本文通过非常快速的方式讲解了如何制作一个PHP 5.2 环境的扩展(PHP Extension),希望能够在图文的方式下让想快速…

oracle11g的安装

目录层次:linux->oracle软件->dbca数据库安装过程:虚拟机->linux->VMtools->拷贝数据库软件->创建一个目录mkdir->创建组.用户->修改根目录->设置参数->解压 >安装->oracle完成参考:安装oracle软件linu…

python 100例(10)

2019独角兽企业重金招聘Python工程师标准>>> 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多…

cocos2dx-3.9 集成admob

Part 1: 安装GoogleMobileAds framework (即admob) 1. 安装Cocoapods,否则解决依赖关系和配置真的会把人不累死也得烦死 sudo gem install cocoapods 国内用户安装过程中可能遇到SSL连接出错的问题,请参考 Cocoapod安装过程中的幺…

用C语言扩展PHP功能

用C语言扩展PHP功能建议读者群:熟悉c,linux,php PHP经过最近几年的发展已经非常的流行,而且PHP也提供了各种各样非常丰富的函数。但有时候我们还是需要来扩展PHP。比如:我们自己开发了一个数据库系统,而且有自己的库函数来操作数…

手把手快速实现 Resnet 残差模型实战

作者 | 李秋键 出品 | AI科技大本营(ID:rgznai100) 引言:随着深度学习的发展,网络模型的深度也随之越来越深,但随着网络模型深度的加深,往往会曾在这随着模型深度的加大,模型准确率反而下降的问…

JHipster开发环境安装

这里采用官方推荐的Yarn安装方法,默认操作系统为CentOS 7.4。 1 安装JDK 推荐版本:OpenJDK 1.8.0-64bit。 完整安装说明,请参考这里 2 安装Nodejs 推荐版本: v8.11.3 完整安装说明,请参考这里 3 安装Yarn 推荐版本&…

用C语言写PHP扩展

用C语言写PHP扩展 1:预定义 在home目录,也可以其他任意目录,写一个文件,例如caleng_module.def 内容是你希望定义的函数名以及参数: int a(int x,int y)string b(string str,int n) 2:到php源码目录的ext目…

Pandas 数据挖掘与分析时的常用方法

今天我们来讲一下用Pandas模块对数据集进行分析的时候,一些经常会用到的配置,通过这些配置的帮助,我们可以更加有效地来分析和挖掘出有价值的数据。数据集的准备这次我们需要用到的数据集是广为人所知的泰坦尼克号的乘客数据,我们…