目录
- Promise解决了什么问题
- Promise的使用
- Promise/A+规范
- 实现一个符合Promise/A+规范的Promise
- 基于观察模式实现Promise
- 【完整版】- 增加then方法实现链式调用
一、Promise解决了什么问题
Promise允许我们通过链式调用的方式来解决“回调地狱”的问题,特别是在异步过程中,通过Promise可以保证代码的整洁性和可读性。本文主要解读Promise/A+规范,并在此规范的基础上,自己实现一个Promise.
二、Promise的使用
1) 创建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) 初始版本myPromise
同时,需要在myPromise的原型上定义链式调用的then方法:
在myPromise里发生状态改变,然后在相应的then方法里面根据不同的状态可以执行不同的操作。
function myPromise(constructor){
let self=this;
console.log('self0:',self, 'this0:',this); // 此处的this指向 new出来的实例对象。
self.status="pending" //定义状态改变前的初始状态
self.value=undefined;//定义状态为resolved的时候的状态
self.reason=undefined;//定义状态为rejected的时候的状态
function resolve(value){
console.log('self:',self, 'this:',this); // 此处的 this 指向window。因为此函数是属于实例对象的参数调用。
//两个==="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);// 异常也是要走reject(e)
}
};
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
console.log('then 中 self:',self, 'then 中 this:',this); // 此处的this指向的是 new出来的Promise实例( 因为调用是在实例对象上直接.then((res)=>{},(rej)=>{}) ),没必要保存起来this。
switch(self.status){
case "resolved":
onFullfilled(self.value); //
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
var p=new myPromise(function(resolve,reject){resolve(111)});
p.then(function(x){console.log(x)})
但是这里myPromise无法处理异步的resolve.比如:
var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});
p.then(function(x){console.log(x)})
//无输出
思考:
为何myPromise无法处理异步的resolve?
五 基于观察模式实现Promise
为了处理异步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";
// 在pending时把状态改变为成功并把数据传过来。
// 并遍历调用成功的回调数组
console.log('构造函数中 resolved中的 成功回调数组:', self.onFullfilledArray);
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);
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "pending": // 为何在 pending 时 push 成功和失败回调函数 到对应的数组中。 (保证了状态的改变是不可逆的)
console.log('then 中的 pending时的 this 与其上value:', self);
self.onFullfilledArray.push(function(){
onFullfilled(self.value)
});
self.onRejectedArray.push(function(){
onRejected(self.reason)
});
console.log('self.onFullfilledArray 成功数组:', self.onFullfilledArray);
case "resolved": // 给传入的函数传参,并调用传入的函数
console.log('then 中的 resolved 时的 this:', self);
onFullfilled(self.value);
break;
case "rejected":
console.log('then 中的 rejected 时的 this:', self);
onRejected(self.reason);
break;
default:
}
}
var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(100)},1000)});
p.then(function(x){console.log(x)})
这样,通过两个数组,在状态发生改变之后再开始执行,这样可以处理异步resolve无法调用的问题。这个版本的myPromise就能处理所有的异步,那么这样做就完整了吗?
没有,我们做Promise/A+规范的最大的特点就是链式调用,也就是说then方法返回的应该是一个promise。
六 完整版-增加then方法实现链式调用
要通过then方法实现链式调用,那么也就是说then方法每次调用需要返回一个primise,同时在返回promise的构造体里面,增加错误处理部分,我们来改造then方法
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";
// 在pending时把状态改变为成功并把数据传过来。
// 并遍历调用成功的回调数组
console.log('构造函数中 resolved中的 成功回调数组:', self.onFullfilledArray);
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);
}
}
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;
}
var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(111)},1000)});
p.then(function(x){console.log(x)}).then(function(){console.log("链式调用1")}).then(function(){console.log("链式调用2")})
//输出
// 链式调用1
// 链式调用2
// 111
这样我们虽然实现了then函数的链式调用,但是还有一个问题,就是在Promise/A+规范中then函数里面的onFullfilled方法和onRejected方法的返回值可以是对象,函数,甚至是另一个promise。
待办:
参考中可见
1) then函数中的onFullfilled和onRejected方法的返回值问题
特别的为了解决onFullfilled和onRejected方法的返回值可能是一个promise的问题。
首先来看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作为参数。
参考
总结
- 在Promise构造函数中,如果状态从
pending变为resolved,那么就遍历执行里面的异步方法
观察者模式理解
1) 目标对象
- 目标对象(被观察者)就是Promise构造函数的
实例, 实例上面存储了成功和失败的回调数组,在这里存储的实际上就是观察者列表。
self.onFullfilledArray=[{},{}];
那么,这些观察者是从哪里来的,push
then中的 push就是在push观察者实例到 观察者列表中。
// 在某个时机执行
在状态从pending变为rejected此时机,执行所有的观察者列表中的观察者对象实例上传入的回调。
2) 观察者
var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});
p.then(function(x){console.log(x)})
p为目标对象。 给目标对象传入的cb为观察者。
当目标对象的状态改变为 成功/失败时, 就执行对应 成功/失败 观察者列表中的 观察者回调
2) 目标对象 观察者 构成分析
- 目标对象 包含:
观察者数组,遍历执行这些观察者实例上传入的回调函数,(也就是调用这些观察者实例对象上的update) - 观察者实例包含:
回调函数,收到目标对象通知时执行,
注意: 此Pomise观察者模式的实现中 观察者没有提供给 目标对象调用的update方法,被目标对象通知时执行的update方法,用于调用 观察者实例传入的 回调函数