用最少的代码手工实现一个Promise,5分钟看懂
栏目: JavaScript · 发布时间: 5年前
内容简介:Promise 采用面向对象的方式封装了回调函数,可以将回调金字塔改为平行的链式写法,优雅的解决了回调地狱,ES7带来了异步的终级解决方案async/await,可以用写同步代码的方式编写异步代码,而Promise正是async/await的基石。Promise 是一种设计模式,也是规范,历史上曾经出现过Promise A/Promise A+/Promise B/Promise D四种规范,最终ES6选择了Promise A+的方案,真理来之不易。Promise表面上看起来比较简单,你看,生成的Promi
Promise 采用面向对象的方式封装了回调函数,可以将回调金字塔改为平行的链式写法,优雅的解决了回调地狱,ES7带来了异步的终级解决方案async/await,可以用写同步代码的方式编写异步代码,而Promise正是async/await的基石。
Promise 是一种设计模式,也是规范,历史上曾经出现过Promise A/Promise A+/Promise B/Promise D四种规范,最终ES6选择了Promise A+的方案,真理来之不易。
Promise表面上看起来比较简单,你看,生成的Promise对象很纯净,只有then,catch,finally几个方法,还有两个隐藏的属性:PromiseStatus和PromiseValue分别表示状态和返回值
说到Promise状态,众所周知,只有pending,fulfilled,rejected 3种状态,而且不可逆,不成功便成仁,从创建实例时的pending,调用成功resolve就变成fulfilled,失败则变为rejected,整个模型非常简单
但是深入了解,Promise还有许多的潜规则,要深入理解一项技术最好的办法是造一个轮子。
Promise能够一统江湖成为异步的终极解决方案(配合async/await),它的价值绝对是不可估量的,值得你亲手实现不止一遍。
极简版Promise
需求分析:
万丈高楼平地起,一砖一瓦靠自己。我们先从最简单的核心功能开始,第一步仅实现Promise的构造器函数和then方法 两个功能
功能清单:
- Promise 构造器函数,传入一个函数,该函数立即执行,并且有resolve和reject两个参数,resolve被调用时Promise状态变为fulfilled
- 实现then方法,传入一个函数,该该数在Promise被fulfilled时执行
代码实现:
class PromiseA { constructor(init) { this.PromiseStatus = 'pending'; var resolve=(val)=>{ if(this.resolveCallback){ this.PromiseStatus="fulfilled" this.resolveCallback(val); } } if(init){ init(resolve,reject); } } then(onFulfill,onReject) { this.resolveCallback=onFulfill; this.rejectCallback=onReject; return this; } } 复制代码
就这么简单,花几分钟就可以写好,写个测试代码跑一下
new PromiseA(function (resolve){ setTimeout(function (){ resolve("hello,from promise 1"); },2000) }).then(function (msg){ console.log(msg); }) 复制代码
两秒后输出了:hello,from promise 1
完美运行,能够转得动的轮子就是好轮子!
但是,好像还缺了点什么?毕竟我们想做的是奔驰车的轮子......
完整版Promise
需求分析
上一步我们做出了第一个能运行起来的Promise,但是还缺失一些必备功能,如下:
-
- 每次调用then方法应该返回一个新的Promise对象
-
- then方法支持链式调用,链式调用有两种用法:
-
2.1 then注册的onFulfill函数没有返回值,则之后的then全部一起触发 复制代码
-
2.2 then注册的onFulfill函数返回了新的promise,则等这个新的promise fulfill之后,再触发之后的then 复制代码
-
- reject函数,以及catch方法
实现思路:
精简版的Promise 很容易实现和读懂,但是要实现链式调用什么的,就有点烧脑了,因为链式调用本身是链表的数据结构,又是高阶函数传来传去,很容易绕晕,我是花了很久时间调试修改,实现思路也是在调试过程中才慢慢理清的,虽然只有几行代码,但是用语言描述比较晦涩难懂,你非得单步调试一下才能明白其中的奥妙。
- 首先,在then方法中返回一个新的promise不是什么难事,new一下就可以了,但是then方法如果返回了promise,要用新的promise替代,问题是then中的promise已经先返回了,这是先有鸡还是先有蛋的问题,时光不能倒流,那只有通过引用传递,改写之前返回的promise了,其实也不用完全替换,只需要改写resolve回调就可以了。
- 对于then方法中不返回promise的情况,复制原promise的resolve回调,就可以同时一起触发多个then回调
完整代码实现:
class PromiseA { constructor(init) { this.PromiseStatus = 'pending'; this.PromiseValue=null; this.resolveCallback=null; this.rejectCallback=null; var resolve=(val)=>{ if(this.resolveCallback){ this.PromiseValue=val; this.PromiseStatus="fulfilled" var promiseNew=this.resolveCallback(val); if(this.nextPromise){ let next=this.nextPromise; if(promiseNew){ //then方法返回了新的promie, promiseNew.resolveCallback=next.resolveCallback; promiseNew.rejectCallback=next.rejectCallback; }else if(next.resolveCallback!=this.resolveCallback){ //没有返回新的promise,需要防重复调用 next.resolveCallback(val); } } } } var reject=(val)=>{ if(this.rejectCallback){ this.PromiseStatus="rejected" this.rejectCallback(val); } } if(init){ init(resolve,reject); } } then(onFulfill,onReject) { this.resolveCallback=onFulfill; this.rejectCallback=onReject; var promise=new PromiseA();//创建一个新的promise实例 promise.resolveCallback=onFulfill;//新的promise实例的resolve函数默认指向当前promise,用来支持多次调用then this.nextPromise=promise;//保存一下新的promise引用,便于链式调用 return promise; } catch(onRejected){ return this.then(null, onRejected); } } 复制代码
写个测试用例跑一下:
console.time("timer1"); console.time("timer2"); new PromiseA(function (resolve){ setTimeout(function (){ resolve("hello,from promise 1"); },2000) }).then(function (msg){ console.log(msg); console.timeEnd("timer1"); }).then(function (msg){ console.log(msg) console.timeEnd("timer2"); }) 复制代码
运行后,两个then回调在2秒后同时触发,说明第一种链式调用验证成功
再测试一下第二种链式调用,测试代码如下:
console.time("timer1"); console.time("timer2"); new PromiseA(function (resolve){ setTimeout(function (){ resolve("hello,from promise 1"); },2000) }).then(function (msg){ console.log(msg); console.timeEnd("timer1"); return new PromiseA(function (resolve){ setTimeout(function (){ resolve("world,from promise 2") },3000) }) }).then(function (msg){ console.log(msg) console.timeEnd("timer2"); }) 复制代码
验证成功,在2秒后触发了第一个then回调,并接收到了hello,from promise1的返回值,在5秒后触发了第二个then回调,并接收到了"world,from promise2"的返回值
实现Promise.all和Promise.race
Promise的实例功能已经完工了,翻翻看Promise构造器函数上还有两个类方法all和race,其中Promise.all是一个非常有用的功能,可以并发执行多个异步任务,全部成功后再执行resolve,无论是处理多个http并行请求,还是并行执行 sql 脚本等并行计算任务,都十分方便。
有了上面的PromiseA类基础设施,实现这个功能简直不要太简单。这次要用静态方法,也叫类方法,就是在PromiseA构造器函数上定义的,es6 的class 中定义的方法默认是生成在实例的原型中的,加一个static关键字就可以变为静态方法。
实现思路:
- 生成一个新的Promise。
- 遍历传入的promise数组,依次调用每一个promise的then方法注册回调。
- 在then 回调中把promise返回值push到一个结果数组中,检测结果数组长度与promise数组长度相等时表示所有promise都已经resolve了,再执行总的resolve。
Promise.race则更简单,只有任意一个promise fulfilled就执行总的resolve。
代码如下:
static all(list){ return new PromiseA(function (resolve){ var results=[]; list.forEach((promise)=>{ promise.then((val)=>{ results.push(val); if(results.length==list.length){ resolve(results); } }) }) }) } static race(list){ return new PromiseA(function (resolve){ list.forEach((promise)=>{ promise.then((val)=>{ resolve(val); }) }) }); } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Igniton 配置手工更新
- SeaGlass:手工搭建伪基站监控系统
- 从手工测试到测试开发,这样学习最高效!
- QQA: Hibernate 为什么需要手工管理双向关联
- 匠心独运解读Mybatis源码,纯手工打造开源框架
- 经验拾忆(纯手工)=> Python好用深度技能工具介绍
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTML5权威指南
[美] Adam Freeman / 谢廷晟、牛化成、刘美英 / 人民邮电出版社 / 2014-1 / 129.00元
《HTML5 权威指南》是系统学习网页设计的权威参考图书。本书分为五部分:第一部分介绍学习本书的预备知识和HTML、CSS 和JavaScript 的最新进展;第二部分讨论HTML 元素,并详细说明了HTML5中新增和修改的元素;第三部分阐述CSS,涵盖了所有控制内容样式的CSS 选择器和属性,并辅以大量代码示例和图示;第四部分介绍DOM,剖析如何用JavaScript 操纵HTML 内容;第五部......一起来看看 《HTML5权威指南》 这本书的介绍吧!