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中回调函数的参数值不是一个thenable对象。
从上面第二点来看:如果
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"。