JavaScript Promise 异步流程控制

Promise 对象可以理解为一次执行的异步操作,使用 promise 对象之后可以使用一种链式调用的方式来组织代码。比如使用 ajax 发一个A请求后,成功后拿到数据,我们需要把数据传给B请求,传统的写法会有多个嵌套一层层写下去,或者把第二个请求提取成另外一个方法,但这样增加了阅读成本,并不能一眼看出这两个请求是流程化两个连续请求;

HelloWorld

使用 Promise 构造方法创建对象

1
2
3
4
5
var promise = new Promise(function(resolve,reject){
// TODO
// 成功调用resolve 往下传递参数 且只接受一个参数
// 失败调用reject 往下传递参数 且只接受一个参数
});

promise 对象实例有如下几个方法用于获取操作结果:

  • then(callback) : 异步操作成功
  • catch(callback) : 异步操作失败
  • then(callback, errorCallback) : 上面两个方法分别只捕获成功/失败的情况,对于 then 的重载方法,可以传入两个 callback,第二个 callback 则用于处理失败的情况;
1
2
3
4
5
6
7
8
var promise = new Promise(function(resolve){
console.log(1);
resolve(3);
});
promise.then(function(value){
console.log(value);
});
console.log(2);

上面的代码执行后的输出结果是 1 2 3;

Promise 的三种状态

Promise 对象有三种状态:

  • Resolve 可以理解为成功的状态;
  • Rejected 可以理解为失败的状态;
  • Pending 既不是 Resolve 也不是 Rejected 状态;该状态是Promise对象实例创建时候的初始状态;

Promise 对象中的resolve方法就是调用then对象的第一个函数,也就是成功的状态;而reject方法就是调用then对象的第二个函数,也就是失败的状态;

Call chaining

1
2
3
4
5
6
7
8
9
10
11
12
var promise = new Promise(function(resolve){
resolve(3);
});
promise.then(function(value){
console.log(value);
return parseInt(value) + 1;
}).then(function (value) {
console.log(value);
return parseInt(value) + 1;
}).then(function (value) {
console.log(value);
})

上面的代码执行后输出结果是 3 4 5,因为每次 then() 调用后,都会返回一个 新的Promise对象,所以可以使用链式调用;如果传入 then()callback 有返回值,则返回的 promise 对象的 then() 函数中 callback 将获取到该返回值;

then() 表示异步操作成功,会返回一个新的 promise 对象,但实际上 catch() 方法也会返回一个新的 promise 对象;

1
2
3
4
5
6
7
8
9
10
11
var promise1 = new Promise(function(resolve){
resolve(3);
});
var thenPromise = promise1.then(function(value){
console.log(value);
});
var catchPromise = thenPromise.catch(function(error){
console.log(error);
});
console.log(promise1 !== thenPromise); // true
console.log(thenPromise !== catchPromise); //true

快速创建 Promise 对象

  • Promise.resolve(value)
  • Promise.reject(error)

Promise.all

假如有两个异步操作,不管他们的先后顺序,需要当这两个操作都成功后执行下一步操作,此时可以使用 Promise.all

1
2
3
4
5
6
7
8
9
10
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});

Promise.race

Promise.race 是只要有一个 promise 对象 resolved 或者 rejected,程序就会停止,且会继续后面的处理逻辑;

1
2
3
4
5
6
7
8
9
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});

由于p1执行较快,Promisethen()将获得结果’P1’。

注:p2仍在继续执行,但执行结果将被丢弃!

JQuery ajax 中的 Promise

一个传统的 ajax 请求通常如下:

1
2
3
4
5
6
$.ajax({
url: '',
dataType:'json',
success: function(data) {
}
});

我们可以使用 Promise 对该请求进行封装:

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
function ajaxPromise(url, method="GET", data={}) {
var promise = new Promise(function (resolve, reject) {
$.ajax({
url: url,
method: method,
params: data,
dataType: 'json',
success: function (data) {
resolve(data);
},
error: function (err) {
reject(err);
}
})
});
return promise;
}
// ...
ajaxPromise('https://api.github.com/emojis')
.then(function (data) {
return data.zzz;
})
.then(function (zzz) {
console.log(zzz);
});

通过把 ajax 方法包装到 Promise 对象之中,使用 resolve()reject() 把异步操作结果返回,这样就可以使用 thencatch 方法了。

JQueryajax 方法,实际上也返回了一个 Promise 对象。

1
2
3
4
5
6
7
8
9
10
11
12
var jqPromise = $.ajax('/api/categories', {
dataType: 'json'
});// 请求已经发送了
// 使用 JQuery Promise 对象
jqPromise.done(function (data) {
ajaxLog('成功, 收到的数据: ' + JSON.stringify(data));
}).fail(function (xhr, status) {
ajaxLog('失败: ' + xhr.status + ', 原因: ' + status);
}).always(function () {
ajaxLog('请求完成: 无论成功或失败都会调用');
});