用最少的代码手工实现一个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,5分钟看懂

说到Promise状态,众所周知,只有pending,fulfilled,rejected 3种状态,而且不可逆,不成功便成仁,从创建实例时的pending,调用成功resolve就变成fulfilled,失败则变为rejected,整个模型非常简单

用最少的代码手工实现一个Promise,5分钟看懂

但是深入了解,Promise还有许多的潜规则,要深入理解一项技术最好的办法是造一个轮子。

用最少的代码手工实现一个Promise,5分钟看懂

Promise能够一统江湖成为异步的终极解决方案(配合async/await),它的价值绝对是不可估量的,值得你亲手实现不止一遍。

极简版Promise

需求分析:

万丈高楼平地起,一砖一瓦靠自己。我们先从最简单的核心功能开始,第一步仅实现Promise的构造器函数和then方法 两个功能

功能清单:

  1. Promise 构造器函数,传入一个函数,该函数立即执行,并且有resolve和reject两个参数,resolve被调用时Promise状态变为fulfilled
  2. 实现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,5分钟看懂

完美运行,能够转得动的轮子就是好轮子!

但是,好像还缺了点什么?毕竟我们想做的是奔驰车的轮子......

完整版Promise

需求分析

上一步我们做出了第一个能运行起来的Promise,但是还缺失一些必备功能,如下:

    1. 每次调用then方法应该返回一个新的Promise对象
    1. then方法支持链式调用,链式调用有两种用法:
  • 2.1 then注册的onFulfill函数没有返回值,则之后的then全部一起触发
    复制代码
  • 2.2 then注册的onFulfill函数返回了新的promise,则等这个新的promise fulfill之后,再触发之后的then
    复制代码
    1. reject函数,以及catch方法

实现思路:

精简版的Promise 很容易实现和读懂,但是要实现链式调用什么的,就有点烧脑了,因为链式调用本身是链表的数据结构,又是高阶函数传来传去,很容易绕晕,我是花了很久时间调试修改,实现思路也是在调试过程中才慢慢理清的,虽然只有几行代码,但是用语言描述比较晦涩难懂,你非得单步调试一下才能明白其中的奥妙。

  1. 首先,在then方法中返回一个新的promise不是什么难事,new一下就可以了,但是then方法如果返回了promise,要用新的promise替代,问题是then中的promise已经先返回了,这是先有鸡还是先有蛋的问题,时光不能倒流,那只有通过引用传递,改写之前返回的promise了,其实也不用完全替换,只需要改写resolve回调就可以了。
  2. 对于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秒后同时触发,说明第一种链式调用验证成功

用最少的代码手工实现一个Promise,5分钟看懂

再测试一下第二种链式调用,测试代码如下:

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,5分钟看懂

实现Promise.all和Promise.race

Promise的实例功能已经完工了,翻翻看Promise构造器函数上还有两个类方法all和race,其中Promise.all是一个非常有用的功能,可以并发执行多个异步任务,全部成功后再执行resolve,无论是处理多个http并行请求,还是并行执行 sql 脚本等并行计算任务,都十分方便。

有了上面的PromiseA类基础设施,实现这个功能简直不要太简单。这次要用静态方法,也叫类方法,就是在PromiseA构造器函数上定义的,es6 的class 中定义的方法默认是生成在实例的原型中的,加一个static关键字就可以变为静态方法。

实现思路:

  1. 生成一个新的Promise。
  2. 遍历传入的promise数组,依次调用每一个promise的then方法注册回调。
  3. 在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);
            })
        })
    });
  }
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

HTML5权威指南

HTML5权威指南

[美] Adam Freeman / 谢廷晟、牛化成、刘美英 / 人民邮电出版社 / 2014-1 / 129.00元

《HTML5 权威指南》是系统学习网页设计的权威参考图书。本书分为五部分:第一部分介绍学习本书的预备知识和HTML、CSS 和JavaScript 的最新进展;第二部分讨论HTML 元素,并详细说明了HTML5中新增和修改的元素;第三部分阐述CSS,涵盖了所有控制内容样式的CSS 选择器和属性,并辅以大量代码示例和图示;第四部分介绍DOM,剖析如何用JavaScript 操纵HTML 内容;第五部......一起来看看 《HTML5权威指南》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具