博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
实现一个完美符合Promise/A+规范的Promise
阅读量:6980 次
发布时间:2019-06-27

本文共 11059 字,大约阅读时间需要 36 分钟。

原文在我的博客中: 如果文章对您有帮助,您的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)})//输出:创建一个新的promisesuccess复制代码

上述是一个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+规范。

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{    //返回的基本类型,直接resolve    resolve(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 复制代码

具体用法请看然后

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->resolved  self.reason=undefined;//if pending->rejected  self.onFullfilledArray=[];//to deal with async(resolved)  self.onRejectedArray=[];//to deal with async(rejeced)  let resolve:IResolve;  resolve=function(value:any):void{    //pending->resolved    if(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 catch  try {    constructor(resolve,reject);  } catch (e) {    reject(e);  }}复制代码

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

转载地址:http://zojpl.baihongyu.com/

你可能感兴趣的文章
SpringCloud核心教程 | 第二篇: 使用Intellij中的maven来快速构建Spring Cloud工程
查看>>
opencv python 模板匹配
查看>>
小程序canvas文本换行生成图片
查看>>
TP5.1.18+swoole实现聊天室
查看>>
【前端数据结构基础】集合
查看>>
响应式布局设计
查看>>
node中 模块导入和导出的探究
查看>>
spring mvc如何计算BEST_MATCHING_PATTERN_ATTRIBUTE
查看>>
jQuery源码 - extend 继承&拷贝 解析
查看>>
[译] ConstraintLayout深入系列之代替常见布局
查看>>
js 5种迭代,遍历方法方法 高程5.2.8
查看>>
Decorator Pattern With Laravel 装饰者模式
查看>>
IBM 340亿美元收购红帽,开源史上最大交易!
查看>>
6天面试、斩获6家硅谷巨头Offer,我是如何做到的?
查看>>
Spark on Angel:Spark机器学习的核心加速器
查看>>
Facebook开源ptr:在Python环境中并行运行单元测试
查看>>
Redis高可用之主从复制实践(四)
查看>>
Joomla模块位置教程
查看>>
以Windows服务方式运行.NET Core程序
查看>>
BootstrapValidator引发的too much recursion
查看>>