Promise 对象

Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。

特点:

  1. 对象的状态不受外界影响Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Fulfilled(已完成,又称 Resolved)和Rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果Promise对象的状态改变只有两种可能:从Pending变为Fulfilled和从Pending变为Rejected

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

缺点:

  1. 无法取消Promise,一旦新建就会立即执行,无法中途取消。
  2. 如果不设置回调函数,Promise内部抛出的错误不会反映到外部。
  3. 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚开始还是即将完成)。

基本用法

一个栗子🌰,用Promise对象实现 Ajax 操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var getJSON = function(url){

var promise = new Promise((resolve, reject) => {
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystagechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();

function handler() {
if(this.readyState !== 4){
return;
}
if(this.status === 200){
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
}
});

return promise;
};

getJSON("/post.json").then(
json => console.log('Contents: ' + json),
error => console.error('出错了', error)
);

resolvereject函数由 JavaScript 引擎提供,不用自己部署。两者在改变 Promise对象的状态的同时,将异步操作的结果或报出的错误作为参数传递出去。

then方法可以接受两个回调函数作为参数(可以说是处理 result 和 err 的回调)。第一个在Promise对象状态变为Fulfilled时调用,第二个(可省)在Promise对象状态变为Rejected时调用,都接受Promise对象传出的值作为参数。

如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例(决定前一个Promise对象的状态),表示异步操作的结果有可能是一个值,也有可能是另一个异步操作。

注意点

1

then方法会返回一个 Promise 实例:

1
2
3
4
var outputPromise = promise.then(
(fulfilled) => {...},
(rejected) => {...},
)

这时,outputPromise变成了受function(fulfilled)或者 function(rejected)控制状态的 Promise 实例了:

  • function(fulfilled)或者 function(rejected)返回一个值(字符串、数组、对象等),那么outputPromise的状态变为Resolved
  • function(fulfilled)或者 function(rejected)抛出异常(throw new Error(...)),那么outputPromise的状态变为Rejected
  • function(fulfilled)或者 function(rejected)返回一个 Promise 实例,那么outputPromise就成为这个新的 Promise 实例。

可以看 promise的传递 的例子(使用了 q)加深理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var Q = require('q');
var defer = Q.defer();
/**
* 通过defer获得promise
* @private
*/
function getInputPromise() {
return defer.promise;
}

/**
* 当inputPromise状态由未完成变成fulfilled时,调用function(fulfilled)
* 当inputPromise状态由未完成变成rejected时,调用function(rejected)
* 将then返回的promise赋给outputPromise
* function(fulfilled) 和 function(rejected) 通过返回字符串将outputPromise的状态由
* 未完成改变为fulfilled
* @private
*/
var outputPromise = getInputPromise().then(function(fulfilled){
return 'fulfilled';
},function(rejected){
return 'rejected';
});

outputPromise.then(function(fulfilled){
console.log('fulfilled: ' + fulfilled);
},function(rejected){
console.log('rejected: ' + rejected);
});

defer.reject(); // 输出 fulfilled: rejected

// defer.resolve(); // 输出 fulfilled: fulfilled

2

在创建新的 Promise 时,作为 Promise 参数传入的函数是会被立即执行的(而不是调用 then 时才执行),只是其中执行的代码可以是异步代码。

虽然 Promise 作为参数接收的函数是同步执行的,但是**then方法的回调函数执行异步**的。

🌰:

1
2
3
4
5
6
7
8
9
10
var p = new Promise(function(resolve, reject){
console.log("create a promise");
resolve("success");
});

console.log("after new Promise");

p.then(function(value){
console.log(value);
});

控制台输出:

1
2
3
"create a promise"
"after new Promise"
"success"

方法

Promise.prototype.then()

then方法返回一个新的Promise实例。链式写法调用then方法时,前一个回调函数将返回结果作为参数,传入第二个回调函数(前一个回调函数没有用return返回结果时,默认返回 undefined)。

Promise.prototype.catch()

reject方法的作用,等同于抛出错误。如果Promise状态已经变成Fulfilled,在resolve语句后再抛出错误是无效的,因为状态不会再改变了。

一般来说,不要在then方法中定义Reject状态的回调函数,总是使用catch方法。因为catch可以捕获之前所有then方法执行中的错误,也更接近同步的try/catch写法。

catch方法返回的也是一个Promise对象。

Promise.all()

Promise.all方法用于将多个Promise实例(否则调用Promise.resolve方法再处理),包装成一个新的Promise实例。接受一个具有Iterator接口,且返回的每个成员都是Promise实例的参数(一般为数组)。

1
var p = Promise.all([p1, p2, p3]);

只有每个成员的状态都为Fulfilled,p 的状态才为Fulfilled,所有返回值组成一个参数传递给 p 的回调函数;否则只要有一个成员被rejected,p 的状态为Rejected,第一个被reject的实例的返回值被传递给 p 的回调函数。

如果作为参数的Promise实例自身定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。

Promise.race()

1
var p = Promise.race([p1, p2, p3]);

p 的状态随第一个改变状态的成员而做相同改变,该成员返回值传递给 p 的回调函数。其他与Promise.all方法一致。

Promise.resolve()

将现有对象转为Promise对象。根据参数分为四种情况:

  1. Promise实例:不作任何修改,直接返回该实例;
  2. thenable对象(具有then方法的对象):转为Promise对象,然后在立即调用其then方法的同时状态变为Resolved
  3. 不符合以上情况的任何参数:返回状态为FulfilledPromise对象,参数传给回调函数;
  4. 不带有任何参数:返回状态为FulfilledPromise对象。

注意:当Promise.resolve()参数是Promise实例时,resolve会“拆箱”获取这个 Promise 实例的状态和值,但这个过程是异步的。

Promise.reject()

返回一个新的Promise实例,状态为Rejected。方法的参数会作为reject的理由,变成后续方法的参数。

reject不具备“拆箱”能力。

应用

  • 加载图片:加载完成时Promise的状态发生变化。
  • Generator 函数与 Promise 的结合:使用Generator函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。

参考资料

写作:

进阶(之后阅读):