promise是用于实现异步编程的一种解决方案,异步操作保存在promise中,在promise状态发生改变时触发对应的异步操作。
Promise的特点
- promise有三种状态,pending(进行中),resolve(已成功),rejected(已失败)。只有异步操作的结果才能改变promise的状态,其他操作无法改变其状态。
- Promise的状态转换只有两种,从pending转为resolve或者从pending转为rejected。一旦转换完成,状态就不再改变。如果状态改变已经发生了,再调用这个Promise对象,会立即返回结果。这与事件不同,事件一旦错过发生的时候,再监听该事件不会得到结果。
- Promise无法取消,一旦新建就会立即执行
- 其内部抛出的错误不会反应到外部
- 处于pending状态时,无法得知当前所处的状态(刚开始进行还是即将完成)
Promise的基本用法
Promise使用Promise的构造方法来构建,该构造方法传入一个函数作为参数,传入的函数有两个参数,resolve和reject。resolve用于将状态由pending转为resolve,reject用于将pending转为rejected。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
resolve和reject中的参数将作为实例生成后then方法中的参数函数中的对应参数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then方法第一个参数为异步操作成功的回调函数,即resolve,第二个参数为异步操作失败的回调函数,即rejected。
then方法的回调函数在当前脚本所有同步任务执行完后才会执行,所以下面的代码的执行顺序与代码顺序不同。
var pro = new Promise((resolve, reject) => {
resolve();
console.log('promise');
});
pro.then(function() {
console.log('resolved');
});
console.log('out');
//promise
//out
//resolved
上面的代码可以看到,promise新建时立即执行了里面的console语句,接着将当前脚本中的同步任务即全局下的console.log(‘out’)执行后才执行then里面的console语句。
resolve和reject函数的参数也可以是另一个promise,此时当前promise的状态由参数的promise决定。
var p1 = new Promise(
(resolve, reject) => {
resolve(p2);
});
var p2 = new Promise(
(resolve, reject) => {
resolve('p2');
}
)
p1.then((value) => {
console.log(value)
})
// p2
调用resolve和reject后接下来的语句仍会执行
var p1 = new Promise(
(resolve, reject) => {
resolve('resolve');
console.log('after');
});
p1.then((value) => {
console.log(value)
})
// after
// resolve
为了避免这种情况,我们将resoleve放在return后
var p1 = new Promise(
(resolve, reject) => {
return resolve('resolve');
console.log('after');
});
p1.then((value) => {
console.log(value)
})
// resolve
Promise的方法
Promise的原型方法
Promise.prototyep.then
then方法用于为Promise提供状态改变时添加对应的回调函数,其两个参数都为函数,第一个参数为Promise状态转为resolve时的回调函数,第二个参数为Promise状态转为rejected时的回调函数。该方法返回一个新的Promise实例(不是原来的Promise对象),即可以使用链式写法多次调用then方法。
getJSON("/posts.json")
.then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
多个then方法链式调用时,会等待上一个then方法返回后才执行下一个。
Promise.prototype.catch
Promise.prototype.catch(reject)可以看成是Promise.prototype.then(null,reject)或Promise.prototype.then(undefined,reject),即该方法是Promise转换状态时发生错误的回调函数。
const promise = new Promise(function(resolve, reject) {
throw new Error('test');});
promise.catch(function(error) {
console.log(error);});
// Error: test
上面代码可以看到,在Promise中抛出了一个错误,catch方法捕获了这个错误。
若Promise已经进入了resolve状态,则抛出错误不会被catch方法捕获,这是因为Promise状态只要转换成resolve或rejected就不会再变换状态的特点导致的。
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
上面代码中,promise已经调用了resolve,所以其下面的抛出异常语句没有被catch方法捕获。
Promise对象抛出的错误如果没被捕获会传递到返回的Promise实例上,知道被捕获为止。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);})
.then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
上面代码中有三个Promise对象,不管哪一个抛出异常,最后都由catch来捕获。
如果没有指定处理错误的回调函数,Promise抛出的错误不会有任何反应,不会影响到外层代码的执行。
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => {
console.log(123)
}, 2000);
// 123
如上面代码中虽然Promise报错了,但是外面的setTimeout方法依然可以执行。
不仅是Promise中会抛出错误,回调函数中也会抛出错误,报错catch中的函数,此时可以再用一个catch来处理抛出的错误。
someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('oh no', error);
// 下面一行会报错,因为y没有声明
y + 2;})
.catch(function(error) {
console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]
Promise.prototype.finally
该方法用于执行Promise不管转换成什么状态都会执行的操作,可以类比try/catch操作中的finally。
Promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
如上代码中,不管promise最后变为什么状态,finally后的参数函数一定会执行。
finally方法的回调函数不接受参数,这意味着无法得知当前是什么状态,所以finally用来执行与状态无关的操作,其本质为then方法的特例。其实现如下代码。
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 })
);
};
finally方法最后会返回resolve或reject的参数值。
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})
转为Promise对象的方法
Promise.resolve
该方法用于将参数转为Promise对象
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
由上面的等价写法我们可以看出Promise.resolve方法返回一个转换为resolve状态的Promise对象,根据其参数有四种不同的返回情况
1.参数为一个Promise实例
此时该方法会直接返回参数中的Promise实例
2.参数是一个有then方法的对象
var thenObj = {
then(resolve) {
console.log('then');
resolve('resolve');
}
}
Promise.resolve(thenObj).then(value => {
console.log(value);
})
// then
// resolve
在使用该方法将对象转为Promise对象时会立即执行该对象的then方法,所以上面代码中首先打印出’then’,然后才执行resolve的回调函数。
3.参数为一个没有then方法的对象或非对象
此时该方法返回resolve的参数为该方法参数的Promise对象
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
4.没有参数
此时该方法直接返回一个Promise对象,由于没有参数,所以此时打印出resolve的参数只能得到undefined
var p = Promise.resolve();
p.then(function (value) {
console.log(value);
});
// undefined
Promise.reject
该方法也接受一个参数,返回一个Promise对象,与Promise.resolve不同的是,该方法返回的Promise对象的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)});
// 出错了
对于不同参数的处理,Promise.reject与Promise.resolve一样
Promise处理多个Promise的方法
Promise.all
该方法用于将多个Promise实例包装为一个新的Promise实例。
该方法接受一个数组作为参数,数组成员为Promise对象,若不是Promise对象,则先使用Promise.resolve方法变为对象。
该方法返回的Promise对象的状态由参数数组中的Promise对象决定,若数组中所有Promise对象的状态都变成已成功,则该方法返回的Promise对象的状态也变为已成功,数组中所有Promise的返回值组成一个数组传递给该方法生成的Promise对象的回调函数;若数组中有一个Promise对象状态变为已失败,则第一个变为已失败的对象的返回值传递给最后返回的Promise对象,该对象状态也变为已失败。
// 生成一个Promise对象的数组
const 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.all的参数数组中有六个成员,只有六个都变为已成功或其中有一个变为已失败,promises的状态才会发生改变,才回调用其回调函数。
Promise.race
该方法的参数和Promise.resolve一样,不同的是,其状态由参数数组成员中率先改变的Promise对象决定,返回的Promise对象会与率先改变的Promise对象相同,率先改变的Promise对象的返回值也会传递给该方法返回的Promise对象。
如果参数数组中的成员不是Promise对象,一样会先使用Promise.resolve将其变为对象。
其他方法
Promise.try
该方法用于模拟try代码块,通过该方法可以使同步函数同步执行,异步函数异步执行。
如下面代码中,database.users.get可能会抛出同步错误也可能抛出异步错误,对应不同的错误写法如下。
//异步错误
database.users.get({id: userId})
.then(...)
.catch(...)
//同步错误
try {
database.users.get({id: userId})
.then(...)
.catch(...)} catch (e) {
// ...}
而通过使用Promise.try可以同时处理这两种错误
Promise.try(() => database.users.get({id: userId}))
.then(...)
.catch(...)
参考自阮一峰的《ECMAScript6入门》