一、什么是Promise
Promise是异步编程的一种解决方案,比传统的解决方案(事件和回调函数)更合理和强大。通俗来讲,promise就是对尚不存在结果的一个替身。
二、Promise基本用法
ES6新增引用类型Promise, 可以通过new操作符来实例化。创建promise对象时需要传入一个执行器(executor)函数作为参数。
下面是一个简单例子:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
- promise状态
- pendding : 初始状态
- fulfilled : 意味着操作成功完成
- rejected : 意味着操作失败
pendding状态可能会变为fulfilled状态并传递一个值给相应的状态处理方法,也可能变为rejected状态并传递失败信息。当其中任何一种情况出现时,状态不会再改变。
2. 通过执行器函数控制promise状态
执行器函数主要有两项职责:初始promise的异步行为和控制状态的最终转换。其中,控制promise状态的转换是通过调用它的两个函数参数实现的。这两个函数参数通常都命名为resolve()和reject()。调用resolve()会把状态切换为兑现,调用reject()会把状态切换为拒绝。另外,调用reject()也会抛出错误。
执行器函数是立即执行的。执行顺序例子:
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
三、Promise原型方法
- Promise.prototype.then()
then()方法接收最多两个参数:onResolved处理程序和onRejected处理程序。这两个参数都是可选的,如果提供的话,则会在promise分别进入“兑现”和“拒绝”状态时执行。传给then()的任何非函数类型的参数都会被静默忽略。
p.then(onResolved[, onRejected])
p.then(value => {
// fulfillment
}, reason => {
// rejection
})
then方法会返回一个新的Promise实例,具体的返回值依据以下规则返回,如果 then 中的回调函数:
-
若调用
then时不传处理程序,则原样向后传。 -
返回了一个值,那么
then返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。 -
如果没有返回任何值,那么
then返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为undefined。 -
抛出一个错误,那么
then返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。 -
返回一个已经是接受状态的 Promise,那么
then返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。 -
返回一个已经是拒绝状态的 Promise,那么
then返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。 -
返回一个未定状态(
pending)的 Promise,那么then返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。
2. Promise.prototype.catch()
Promise.prototype.catch()方法用于给promise添加拒绝处理程序。这个方法只接收一个参数:onRejected处理程序。事实上,这个方法就是一个语法糖,调用它就相当于调用Promise.prototype.then(null, onRejected)。
在返回新promise实例方面,Promise.prototype.catch()的行为与Promise.prototype.then()的onRejected处理程序是一样的。
3. Promise.prototype.finally()
Promise.prototype.finally()方法用于给promise添加onFinally处理程序,这个处理程序在promise转换为解决或拒绝状态时都会执行。这个方法可以避免onResolved和onRejected处理程序中出现冗余代码。但onFinally处理程序没有办法知道promise的状态是解决还是拒绝,所以这个方法主要用于添加清理代码。
在返回新promise实例方面,因为onFinally被设计为一个状态无关的方法,所以多数情况下它都会原样后传父promise。无论父promise是解决还是拒绝,都会原样后传。如果返回的是一个待定的promise,或者onFinally处理程序抛出了错误(显式抛出或返回了一个拒绝promise),则会返回相应的promise(待定或拒绝)。
四、Promise链式调用
1. promise链
连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。之所以可以这样做,是因为每个promise实例的方法(then()、catch()和finally())都会返回一个新的promise对象,而这个新promise又有自己的实例方法。这样连续调用就可以构成所谓的“promise链”。比如:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
**注意:**一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。
2. promise图
因为一个promise可以有任意多个处理程序,所以promise链可以构建有向非循环图的结构。这样,每个promise都是图中的一个节点,而使用实例方法添加的处理程序则是有向顶点。因为图中的每个节点都会等待前一个节点落定,所以图的方向就是promise的解决或拒绝顺序。
下面的例子展示了一种promise有向图,也就是二叉树:
// A
// / \
// B C
// /\ /\
// D E F G
let A = new Promise((resolve, reject) => {
console.log('A');
resolve();
});
let B = A.then(() => console.log('B'));
let C = A.then(() => console.log('C'));
B.then(() => console.log('D'));
B.then(() => console.log('E'));
C.then(() => console.log('F'));
C.then(() => console.log('G'));
// A
// B
// C
// D
// E
// F
// G
注意,日志的输出语句是对二叉树的层序遍历。如前所述,promise的处理程序是按照它们添加的顺序执行的。由于promise的处理程序是先添加到消息队列,然后才逐个执行,因此构成了层序遍历。
树只是promise图的一种形式。考虑到根节点不一定唯一,且多个promise也可以组合成一个promise(通过下一节介绍的Promise.all()和Promise.race()),所以有向非循环图是体现promise链可能性的最准确表达。
五、Promise静态方法
- Promise.resolve()
Promise.resolve的作用就是快速地把一个值转为一个Promise对象
Promise.resolve ('foo')
.then (function (value) {
console.log(value)
})
// 等同于
new Promise (function (resolve, reject) { resolve('foo')})
.then (function (value) {
console.log(value)
})
Promise.resolve如果接收到的是另一个Promise对象,那这个对象会被原样返回
var promise = ajax('XXX')
var promise2 = Promise.resolve(promise)
console.log(promise === promise2) // true
2. Promise.reject()
它的作用就是快速创建一个一定是rejected状态的Promise
let p2 = Promise.reject();
// 等同于
let p1 = new Promise((resolve, reject) => reject());
无论传入什么参数, 它都会作为promise失败的理由
六、Promise合成
Promise类提供两个将多个promise实例组合成一个promise的静态方法:Promise.all()和Promise.race()。而合成后promise的行为取决于内部promise的行为。
1. Promise.all
**Promise.all(iterable)** 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。
let p1 = Promise.all([
Promise.resolve(),
Promise.resolve()
]);
// 可迭代对象中的元素会通过Promise.resolve()转换为promise
let p2 = Promise.all([3, 4]);
// 空的可迭代对象等价于Promise.resolve()
let p3 = Promise.all([]);
// 无效的语法
let p4 = Promise.all();
// TypeError: cannot read Symbol.iterator of undefined
合成的promise只会在每个包含的promise都解决之后才解决:
let p = Promise.all([
Promise.resolve(),
new Promise((resolve, reject) => setTimeout(resolve, 1000))
]);
setTimeout(console.log, 0, p); // Promise <pending>
p.then(() => setTimeout(console.log, 0, 'all() resolved!'));
// all() resolved!(大约1秒后)
如果至少有一个包含的promise待定,则合成的promise也会待定。如果有一个包含的promise拒绝,则合成的promise也会拒绝:
// 永远待定
let p1 = Promise.all([new Promise(() => {})]);
setTimeout(console.log, 0, p1); // Promise <pending>
// 一次拒绝会导致最终promise拒绝
let p2 = Promise.all([
Promise.resolve(),
Promise.reject(),
Promise.resolve()
]);
setTimeout(console.log, 0, p2); // Promise <rejected>
// Uncaught (in promise) undefined
如果所有promise都成功解决,则合成promise的解决值就是所有包含promise解决值的数组,按照迭代器顺序:
let p = Promise.all([
Promise.resolve(3),
Promise.resolve(),
Promise.resolve(4)
]);
p.then((values) => setTimeout(console.log, 0, values)); // [3, undefined, 4]
如果有promise拒绝,则第一个拒绝的期promise将自己的理由作为合成promise的拒绝理由。之后再拒绝的promise不会影响最终promise的拒绝理由。不过,这并不影响所有包含promise正常的拒绝操作。合成的期promise静默处理所有包含promise的拒绝操作,如下所示:
// 虽然只有第一个promise的拒绝理由会进入
// 拒绝处理程序,第二个promise的拒绝也
// 会被静默处理,不会有错误跑掉
let p = Promise.all([
Promise.reject(3),
new Promise((resolve, reject) => setTimeout(reject, 1000))
]);
p.catch((reason) => setTimeout(console.log, 0, reason)); // 3
// 没有未处理的错误
- Promise.race()
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
let p1 = Promise.race([
Promise.resolve(),
Promise.resolve()
]);
// 可迭代对象中的元素会通过Promise.resolve()转换为promise
let p2 = Promise.race([3, 4]);
// 空的可迭代对象等价于new Promise(() => {})
let p3 = Promise.race([]);
// 无效的语法
let p4 = Promise.race();
// TypeError: cannot read Symbol.iterator of undefined
Promise.race()不会对解决或拒绝的promise区别对待。无论是解决还是拒绝,只要是第一个落定的promise,Promise.race()就会包装其解决值或拒绝理由并返回新promise:
// 解决先发生,超时后的拒绝被忽略
let p1 = Promise.race([
Promise.resolve(3),
new Promise((resolve, reject) => setTimeout(reject, 1000))
]);
setTimeout(console.log, 0, p1); // Promise <resolved>: 3
// 拒绝先发生,超时后的解决被忽略
let p2 = Promise.race([
Promise.reject(4),
new Promise((resolve, reject) => setTimeout(resolve, 1000))
]);
setTimeout(console.log, 0, p2); // Promise <rejected>: 4
// 迭代顺序决定了落定顺序
let p3 = Promise.race([
Promise.resolve(5),
Promise.resolve(6),
Promise.resolve(7)
]);
setTimeout(console.log, 0, p3); // Promise <resolved>: 5
如果有一个promise拒绝,只要它是第一个落定的,就会成为拒绝合成promise的理由。之后再拒绝的promise不会影响最终promise的拒绝理由。不过,这并不影响所有包含promise正常的拒绝操作。与Promise.all()类似,合成的promise会静默处理所有包含promise的拒绝操作,如下所示:
// 虽然只有第一个promise的拒绝理由会进入
// 拒绝处理程序,第二个promise的拒绝也
// 会被静默处理,不会有错误跑掉
let p = Promise.race([
Promise.reject(3),
new Promise((resolve, reject) => setTimeout(reject, 1000))
]);
p.catch((reason) => setTimeout(console.log, 0, reason)); // 3
// 没有未处理的错误