Promise的几种状态?
Promise
有三种状态:未定(pending)、接受(fulfillment)和拒绝(rejection)。并且,只能由未定状态变为接受状态,或者由未定状态变为拒绝状态。
它的一般写法是:
var promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise的状态何时发生改变?
上面代码中,只有在调用resolve
(/reject
)回调函数时,这个Promise
的状态才变成接受(/拒绝)状态。也就是说,在执行这两个回调函数中的任意一个之前,这个Promise
都是未定状态的。
如果两个回调函数相继出现,终态是什么?
考虑下面代码及其结果:
new Promise(function(success,error){
console.log("before success");
success();
error();
console.log("after error");
}).then(()=>console.log("success"),
()=>console.log("error"))
//输出 before success
//输出 after error
//输出 success
new Promise(function(success,error){
console.log("before error");
error();
success();
console.log("after success");
}).then(()=>console.log("success"),
()=>console.log("error"))
//输出 before error
//输出 after success
//输出 error
Promise的状态改变后还会再改变吗?
(
Promise
) 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变……状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。
——《ES6标准入门(第二版)》
如何指定两种状态的回调函数?
可以用then
方法分别指定接受状态和拒绝状态的回调函数:
promise.then(function(value) {
// 接受状态的回调函数
}, function(error) {
// 拒绝状态的回调函数
});
then
方法有两个参数,第一个指定接受状态的回调函数,第二个指定拒绝状态的回调函数。因为Promise
状态固定后不会再变,所以这两个回调函数有且只有一个在将来会被执行。
下面用例子说明状态确定和状态凝固的行为:
var Pro=new Promise(function(success,reject){
success();
})
上面代码创建一个Promise
:
- 由于
Promise
一旦创建就会立刻调用。所以上述代码会执行success
回调函数。 - 由于
success
回调函数被执行,说明这个Promise就是接受状态,且永远只能是接受状态。
由于Pro
变量已经是接受状态了,所以此后,Pro
调用then
方法,只有第一个回调函数会被执行(它指定接受状态的回调行为):
//一些其他代码.....
Pro.then(()=>console.log("success"),()=>console.log("Error"))
//输出:success
相似的,如果上面的构造函数中,执行reject
方法,那么Pro
状态就会被固定为拒绝状态,那么此后,Pro
调用then
方法,只有第二个回调函数会被执行(它指定拒绝状态的回调行为)。
then中的回调函数和Promise构造函数中的参数函数一样吗?
即问:
var p = new Promise(function(resolve, reject) {
resolve(1);
});
function success(){}
p.then(success);
在上述代码中。resolve
和success
是一样的吗?
并不一样。
resolve
是指用一个值来完成这个promise
,只有promise
resolve
了,它才变为接受状态,从而then
方法注册的回调函数success
才会被执行。换句话说,就算没有then
也没有success
,只要有resolve
,它就可以变成接受状态。因果关系不能颠倒。
而它们的行为与resolve
中的参数有关:参数是否是thenable
类型参数(即可调用then
的对象,这里就是指Promise
对象):
如果值是非
thenable
类型,如数值、字符串、数组等,它就会传入then
中的参数回调函数success
中执行。-
如果值是
thenable
类型,比如Promise
对象。为了更好说明,使用下面示例代码:var p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) }); var p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) }); p2 .then(result => console.log(result)) .catch(error => console.log(error))
在上面代码中,
p2
在1秒之后调用resolve
,然而resolve
中传入的是一个Promise
对象p1
作为参数,此时,p2
的终态由p1
来决定:尽管1秒后p2
调用了resolve
,似乎应该是接受状态,但是因为p1
在3秒后状态才改变,目前它仍然是未定态,从而此时p2
也是未定态。
这很好理解,resolve
没有实际作用,它只是用来标记状态改变的一个函数:- 当它的参数不是
Promise
对象时,表明至此结束,可以进入接受状态,这个值可以传递给then
中的回调函数。 - 当它的参数是另一个
Promise
对象时(记为otherPro
),表明尚未结束,有赖于另一个Promise
的状态,只有当另一个Promise
状态确定,那么它的状态也就随之确定。此时,调用then
中的回调函数时,当然不能将otherPro
作为参数传入,因为在概念上就不合理,没有意义。那么这个参数是什么呢?它就是otherPro
中的resolve
(/reject
)的参数。当然,如果otherPro
的resolve
(/reject
)的参数又是一个Promise
,则then
的回调函数的参数就是它中的resolve
(/reject
)的参数,以此类推。原则上:务必保证then
中回调函数的参数值不是一个then
able对象。
从上面第二点来看:如果
p2
中reject
或resolve
了p1
,则p1
的终态决定了p2
的终态;p2
的回调函数的参数就是p1
的resolve
/reject
参数。所以,p2
后面的then
其实完全可以理解为是调用在p1
上的。
这里,p1
使用reject(new Error("..."))
,表明它成为拒绝状态并抛出错误,但是,p2
后的then
(已知,可以认为它是调用在p1
上的)中缺少第二个参数,说明这个then
对于p1
无法处理,则then
会返回一个新的Promise
,它是拒绝状态(与p1
状态一致),且它的内部属性[[PromiseValue]]
会保留(引用)这个值,然后转发。在它上面又调用catch
方法(catch
方法是then(null,function(){/*..*/})
的简写,即仅指定第二个处理拒绝状态的回调函数),它就可以处理了。 - 当它的参数不是
then的返回值是什么?
then
是有返回值的,它返回一个新的Promise
(与此前任何一个Promise
都不同)。然而这个Promise
的行为与then
中回调函数的返回值有关:
-
如果
then
中的回调函数返回一个值,那么then
返回的Promise
将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。new Promise(function(success,reject){ success()//确定是接受状态 }) .then(()=>"hello!") //then中回调函数返回一个字符串值 //上面then返回一个新的Promise,与下面的then进行链式调用 .then((value)=>console.log("success! "+value), //第一个参数回调函数被执行 (value)=>console.log("Error! "+value)) //输出:success! hello!
在上面代码中,由于
Promise
一经创建立刻调用,所以在第一个then
之前,该Promise
已经是接受状态。所以,第一个then
中的回调函数会被执行,它返回了一个值(字符串),那么then
将会返回一个新的Promise
(请不要混淆then
的返回值和then
中回调函数的返回值,代码注释已做区别),并且这个新的Promise
就是接受状态。
第二个then
就是针对这个新的Promise
而调用,不出所料,第一个参数回调函数被执行。并且值得注意,返回的值(字符串"hello!")也将会作为回调函数的第一个参数传入,如你所见,打印出“success! hello!”。
【当然,如果then
中的参数回调函数根本没有返回值,那么就相当于返回undefined
,此时then
返回的新的Promise
仍然是接受状态,但是值是undefined
。】 -
如果
then
中的回调函数抛出一个错误,那么then
返回的Promise
将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。(这个与第1个的情况相似,不讨论。)
-
如果
then
中的回调函数返回一个已经是接受状态的Promise
,那么then
返回的Promise
也会成为接受状态,并且那个Promise
的接受状态的回调函数的参数值也将会作为该被返回的Promise
的接受状态回调函数的参数值。这一条与第1条也有相似之处。我们假设已经有一个成为接受状态的
Promise
:var Pro=new Promise(function(success,error){ success("hello!"); //回调函数的参数是"hello!" });
"hello!"就是上面所说的“那个
Promise
的接受状态的回调函数的参数值”,同时上面也说:“……也将会作为该被返回的Promise
的接受状态回调函数的参数值”,下面的代码可以说明:new Promise(function(resolved,rejected){ resolved(); //接受状态 }) .then(()=>Pro) //保证会返回Pro .then((value)=>console.log("success "+value), //接受状态回调函数被执行 (value)=>console.log("error "+value)); //输出:success hello!
上面,第一个
then
的回调函数返回了已经是接受状态的Pro
,所以,Pro
上调用的then
(第二个then
)的第一个参数回调函数被执行,打印信息。同时,因为"hello!"是Pro
的接受状态的回调函数的参数值,所以它也是then
返回的新的Promise
的接受状态的回调函数的第一个参数。 -
如果
then
中的回调函数返回一个已经是拒绝状态的Promise
,那么then
返回的Promise
也会成为拒绝状态,并且将那个Promise
的拒绝状态的回调函数的参数值作为该被返回的Promise
的拒绝状态回调函数的参数值。(这个与第3个的情况相似,不讨论。)
-
如果
then
中的回调函数返回一个未定状态(pending)的Promise
,那么then
返回Promise
的状态也是未定的,并且它的终态与那个Promise
的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise
变为终态时的回调函数的参数是相同的。这个比前面的理解的复杂。关键是理解:“……并且它的终态与那个
Promise
的终态相同”。假定我们有一个Promise
,它在五秒之后才会变为接受状态:var Pro=new Promise(function(success){ setTimeout(()=>success("Hello"),5000); //保持5秒未定态,5秒后为接受态 });
则:
new Promise(function(resolved){ resolved(); console.log("waiting for 5s..."); }) .then(()=>Pro) .then((value)=>console.log(value+" in France is bonjour")); //输出:waiting for 5s... //(在此等待5秒) //输出:Hello in France is bonjour
上面先使得匿名的
Promise
变为接受态,然后输出提示语句(“waiting for 5s...”)。这样,第一个then
的回调函数会返回Pro
。
关键在于:此时Pro
显然还是未定态,所以第一个then
返回新的Promise
也是未定态,所以第二个then
中的回调函数目前不做反应。等待5秒后,Pro
变成了接受态,从而then
返回的新的Promise
也就相应变成接受态,从而第二个then
中的回调函数被激活,打印出信息,不出所料,它的参数正是Pro
中接受态的回调函数的参数"Hello"。