(一)什么是 Promise?
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
(二)Promise中有3个状态
pending: 进行状态(未完成状态).
fulfilled: 成功的操作.
rejected: 失败的操作.
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
(三)resolve 和 reject
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。
①resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”或者"失败"(即从Pending变为Resolved或者Rejected),在异步操作成功或者失败时调用,并将异步操作的结果,作为参数传递出去;
②reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
注意: 如果传给 resolve(…) 的是一个非 Promise、非 thenable 的立即值,这个 promise 就会用这个值完成。如果传给 resolve(…) 的是一个真正的 Promise 或 thenable 值,这个值就会被递归展开,并且(要构造的)promise 将取用其最终决议值或状态。reject(…) 不会像 resolve(…) 一样进行展开。 如 果 向reject(…) 传入一个 Promise/thenable 值,它会把这个值原封不动地设置为拒绝理由。
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
//Promise(..) 构造器的第一个参数回调会展开 thenable(和 Promise.resolve(..) 一样)或真正的 Promise
var rejectedPr = new Promise( function(resolve,reject){
// 用一个被拒绝的promise完成这个promise
resolve( Promise.reject( "Oops" ) ); //这里调用resolve将会进入失败状态
} );
rejectedPr.then(
function fulfilled(){
// 永远不会到达这里
},
function rejected(err){
console.log( err ); // "Oops"
}
);
Promise.resolve(…): resolve(决议)是一个精确的好名字,因为它实际上的结果可能是完成或拒绝。
var fulfilledTh = {
then: function(resolve) { resolve( 42 ); }
};
var rejectedTh = {
then: function(resolve,reject) {
reject( "Oops" );
}
};
var p1 = Promise.resolve( fulfilledTh );
var p2 = Promise.resolve( rejectedTh );
// p1是完成的promise
// p2是拒绝的promise
Promise.reject(…): 创建一个已被拒绝的 Promise.
//以下两个promise 是等价的:
var p1 = new Promise( function(resolve,reject){
reject( "Oops" );
} );
var p2 = Promise.reject( "Oops" );
Promise.resolve与Promise.reject的异同
相同点:
如果传给Promise.resolve(…)/ Promise.reject(…) 的是一个非 Promise、非 thenable的立即值,这个 promise 就会用这个值完成/拒绝。
不同点:
Promise.resolve(…) 会递归展开 thenable 值,但Promise.reject不会展开,会将原值设为拒绝理由。
如果传入的是真正的 Promise,Promise.resolve(…) 什么都不会做,只会直接把这个值返回,是不会有额外的开销的。而Promise.reject会将Promise原值设为拒绝理由返回。
(四)then的用法(Fullfilled与Rejected)
Promise实例生成以后,可以用then方法分别指定Fullfilled状态和Reject状态的回调函数。
then方法可以接受两个回调函数作为参数。
第一个回调函数是Promise对象的状态变为Fullfilled时调用,毫无疑义,总是处理完成的情况,所以不需要使用标识两种状态的术语“resolve”。
第二个回调函数是Promise对象的状态变为Reject时调用。
其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
注意:
1)如果完成或拒绝回调中抛出异常,返回的 promise 是被拒绝的。
2)如果任意一个回调返回非 Promise、非 thenable 的立即值,这个值会被用作返回 promise 的完成值。
3)如果完成处理函数返回一个 promise 或 thenable,那么这个值会被展开,并作为返回promise 的决议值。
promise.then(function fulfilled(value) {
// success
}, function rejected(err) {
// failure
});
(五)catch的用法
catch(…) 只接受一个拒绝回调作为参数,并自动替换默认完成回调。换句话说,它等价于 then(null,…)。
p.catch( rejected ); // 或者p.then( null, rejected )
(六)Promise的链式流
链式流程控制可行的 Promise 固有特性:
①每次你对 Promise 调用 then(…),它都会创建并返回一个新的 Promise,我们可以将其链接起来;
②在完成(fullfilled)或拒绝(rejected)处理函数内部,如果返回一个值或抛出一个异常,新返回的(可链接的)Promise 就相应地决议(走fullfilled或走rejected)。
// Promise-aware ajax
function request(url) {
return new Promise( function(resolve,reject){
// ajax(..)回调应该是我们这个promise的resolve(..)函数
ajax( url, resolve ); //下面的例子基于 在ajax回调中调用了resolve函数
} );
}
// 步骤1:
request( "http://some.url.1/" )
// 步骤2:
.then( function(response1){
foo.bar(); // undefined,出错!
// 永远不会到达这里
return request( "http://some.url.2/?v=" + response1 );
} )
// 步骤3:
.then(
function fulfilled(response2){
// 永远不会到达这里
},
// 捕捉错误的拒绝处理函数
function rejected(err){
console.log( err );
// 来自foo.bar()的错误TypeError
return 42;
}
)
// 步骤4:
.then( function(msg){
console.log( msg ); // 42
} );
③如果完成或拒绝处理函数返回一个 Promise,它将会被展开,这样一来,不管它的决议值是什么,都会成为当前 then(…) 返回的链接 Promise 的决议值。
var p = Promise.resolve( 21 );
p.then( function(v){
console.log( v ); // 21
// 创建一个promise并返回
return new Promise( function(resolve,reject){resolve( v * 2 ); } ); })
.then( function(v){
console.log( v ); // 42
} );
Promise链式调用的一些特例:
/*我们可以构建这样一个序列:不管我们想要多少个异步步骤,每一步都能够根据需要等待下一步.*/
function delay(time) {
return new Promise( function(resolve,reject){setTimeout(resolve,time);} );
}
delay( 100 ) // 步骤1
.then( function STEP2(){
console.log( "step 2 (after 100ms 运行)" );
return delay( 200 );
} )
.then( function STEP3(){
console.log( "step 3 (after another 200ms 运行)" );
} )
/*如果没有给then(..)传递一个适当有效的函数作为完成处理函数参数,还是会有作为替代的一个默认处理函数*/
var p = Promise.resolve( 42 );
p.then(
// 假设的完成处理函数,如果省略或者传入任何非函数值
// fullfilled函数置为 null 等价于 function(v) {return v; }
null,
function rejected(err){
// 永远不会到达这里
}
)
.then(
function fullfilled(msg){
console.log(msg)//42
} ,
function rejected(err){
console.log(err)//不会运行到这里
}
);;
(七)Promise对比回调机制
回调方式:
function add(getX,getY,cb) {
var x, y;
getX( function(xVal){
x = xVal;
//对异步函数做的兼容处理(有可能还没取到值)
if (y != undefined) {
send( x + y ); // 发送求和结果
}
} );
getY( function(yVal){
y = yVal;
// 两个都准备好了?
if (x != undefined) {
send( x + y ); // 发送求和结果
}
} );
}
// fetchX() 和fetchY()是同步或者异步函数
add( fetchX, fetchY, function(sum){
console.log( sum );
} );
为了能够顺利执行发送求和结果,需要添加特定的逻辑来做一些统一的异步处理。
Promise:
function add(xPromise,yPromise) {
// Promise.all([ .. ])接受一个promise数组并返回一个新的promise,这个新promise等待数组中的所有promise完成
return Promise.all( [xPromise, yPromise] )
// 这个promise决议之后,我们取得收到的X和Y值并加在一起
.then( function(values){
// values是来自于之前决议的promise的消息数组
return values[0] + values[1];
} );
}
// fetchX()和fetchY()返回相应值的promise,可能已经就绪,也可能以后就绪
add( fetchX(), fetchY() )
// 我们得到一个这两个数组的和的promise
// 现在链式调用 then(..)来等待返回promise的决议
.then( function(sum){
console.log( sum );
} );
promise 归一保证了行为的一致性。我们可以按照不依赖于时间的方式追踪值 X 和 Y。它们是未来值。
(八)Promise完成事件
考虑如下代码,foo是一个Promise:
function foo(x) {
// 可是做一些可能耗时的工作
// 构造并返回一个promise
return new Promise( function(resolve,reject){
// 最终调用resolve(..)或者reject(..)
// 这是这个promise的决议回调
} );
}
var p = foo( 42 );
方式一:不论 foo(…) 成功与否,bar(…),baz(…) 都会被调用。并且如果收到了foo(…) 失败的通知,它会亲自处理自己的回退逻辑。
bar( p );
baz( p );
function bar(fooPromise) {
// 监听foo(..)完成
fooPromise.then(
function(){
// foo(..)已经完毕,所以执行bar(..)的任务
},
function(){
// 啊,foo(..)中出错了!
}
);
}
// 对于baz(..)也是一样
方式二:bar(…)、baz(…) 只有在 foo(…) 成功时才会被调用,否则就会调用 oppsBar(…),oppsBaz(…)。
function bar() {
// foo(..)肯定已经完成,所以执行bar(..)的任务
}
function oopsBar() {
// 啊,foo(..)中出错了,所以bar(..)没有运行
}
// 对于baz()和oopsBaz()也是一样
var p = foo( 42 );
p.then( bar, oopsBar );
p.then( baz, oopsBaz );
两段代码都以使用 promise p 调用 两次then(…) 结束(bar、baz)。这个事实说明了前面的观点, 就是Promise(一旦决议)一直保持其决议结果(完成或拒绝)不变,可以按照需要多次 查看。
(九)Promise的优缺点
优点:
1)有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
2)一旦 Promise 决议,它就永远保持在这个状态。此时它就成为了不变值(immutable value),可以根据需求多次查看。
3)Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点:
首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。
其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。