ES6 Learning - Promise对象

含义

Promise是异步编程的一种解决方案。语法角度来说可以从它来获取异步操作的消息。

Promise对象具有以下特点。

  1. 对象的状态不受外界影响。三种状态:Pending进行中,Resolved已完成和Rejected已失败。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。

Promise对象的缺点:

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

基本用法

Promise新建后就会立即执行。

1
2
3
4
5
6
7
8
9
10
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});

promise.then(function() {
console.log('Resolved.');
});

console.log('Hi!');

下面是一个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(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = 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("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});

Promise.prototype.then()

then(resolveFn, rejectFn)

Promise.prototype.catch()

一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});

// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});

如果没有catch方法指定错误回调函数,Promise对象抛出的错误不会传递到外层代码,导致运行后没有任何输出。

Chrome不遵守这条规定,会抛出ReferenceError: x is not defined的错误。Promise对象依旧会变成rejected状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2); // or var y = x + 2;
});
};

var p = someAsyncThing();
p.then(function() {
console.log('everything is great');
})
.catch(function(err) {
console.log(err);
});
// Chrome报错,Firefox不报错
p; // Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: ReferenceError: x is not defined at ...}

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
var promise = new Promise(function(resolve, reject) {
resolve("Ok");
reject("Error");
resolve("Ok again");
});
promise.then(function(value) {
console.log(value);
throw new Error("Error");
})
.catch(function(err) {
console.log(err);
})
.then(function() {
console.log("continue...");
});
// Ok
// Error: Error(…)
// continue..
promise; // Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "Ok"}
promise.then(function(value) {
console.log(value);
});
promise;
// Ok
// Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "Ok"}

Promise.all()

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

参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都必须是Promise实例。

p的状态由p1p2p3决定。

只有三个状态都变成resolvedp的状态才会变成resolved,此时返回值是p1p2p3的返回值组成的数组。

只要有一个被rejectedp的状态就会变成rejected,此时第一个被reject的promise实例的返回值会传递给p的回调函数。

1
2
3
4
5
6
7
8
9
10
// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON("/post/" + id + ".json");
});

Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});

Promise.race()

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

只要p1p2p3中有一个实例率先改变状态,p的状态就跟着改变。

Promise.resolve()

上面两个方法都会自动做判断参数是否是Promise对象,如果不是,则会调用Promise.resolve()方法转为Promise对象。

1
2
3
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

参数分为四种情况。

参数是Promise实例,则不做任何修改,直接返回实例。

参数是thenable对象,会转为Promise对象,然后立即执行对象的then方法。

1
2
3
4
5
6
7
8
9
10
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});

参数不是具有then方法的对象,或根本不是对象,则返回新的Promise对象,状态为resolved

1
2
3
4
5
6
7
8
9
10
11
var p = Promise.resolve(function() {
console.log("Hello");
});

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

// function () {
// console.log("Hello");
// }

不带任何参数,则返回一个普通的resolved的Promise对象。

1
2
3
4
5
var p = Promise.resolve();

p.then(function () {
// ...
});

Promise.reject()

方法与Promise.resolve()一样,只是返回实例的状态为rejected

1
2
3
4
5
6
7
var p = Promise.reject('出错了');
// 等同于
var p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s){
console.log(s)
});

附加方法

done

如果以thencatch结尾,那么最后一个方法抛出的错误都可能无法捕捉。因此提供一个done方法保证回调链尾端总能捕捉任何可能出现的错误。

1
2
3
4
5
6
7
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 抛出一个全局错误
setTimeout(() => { throw reason }, 0);
});
};

finally

类似jqueryajax的回调函数always

1
2
3
4
5
6
7
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};