promise实现原理
Posted yayaxuping
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了promise实现原理相关的知识,希望对你有一定的参考价值。
简要介绍: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{ //返回的基本类型,直接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实现原理的主要内容,如果未能解决你的问题,请参考以下文章