用最少的代码手工实现一个Promise,5分钟看懂
栏目: JavaScript · 发布时间: 6年前
内容简介: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好用深度技能工具介绍
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Iterative Methods for Sparse Linear Systems, Second Edition
Yousef Saad / Society for Industrial and Applied Mathematics / 2003-04-30 / USD 102.00
Tremendous progress has been made in the scientific and engineering disciplines regarding the use of iterative methods for linear systems. The size and complexity of linear and nonlinear systems arisi......一起来看看 《Iterative Methods for Sparse Linear Systems, Second Edition》 这本书的介绍吧!