ES6新增了正式Promise(期约)引用类型。
同步和异步
同步行为:对应内存中顺序执行的处理器指令。每条指令都会严格按照出现顺序执行,执行后 会立刻获得存储在系统本地(寄存器or系统内存)
异步行为:类似系统中断,当前进程外部的实体可以触发代码执行。
以往的异步变成模式
早起的javascript只支持定义回调函数来表明异步操作。串联多个异步操作就需要敲深度嵌套回调函数(“回调地狱”)。
异步返回值
function double(value, callback) {
setTimeout(() => callback(value * 2), 1000);
}
double(3, (x) => console.log(`I was given: ${x}`));
// I was given: 6(大约 1000 毫秒之后)
失败处理(不推荐了)
function double(value, success, failure) {
setTimeout(() => {
try {
if (typeof value !== 'number') {
throw 'Must provide number as first argument';
}
success(2 * value);
} catch (e) {
failure(e);
}
}, 1000);
}
const successCallback = (x) => console.log(`Success: ${x}`);
const failureCallback = (e) => console.log(`Failure: ${e}`);
double(3, successCallback, failureCallback);
double('b', successCallback, failureCallback);
// Success: 6(大约 1000 毫秒之后)
// Failure: Must provide number as first argument(大约 1000 毫秒之后)
Promise三个状态
- 待定pending (未开始或者执行中)
- 兑现、解决resolved(成功完成)
- 拒绝 rejected(没有成功完成)
通过执行函数控制期约状态
Promise状态是私有的,所以只能在内部操作。内部操作在Promise的执行器函数中完成。
执行器函数的两大职责:
- 初始化Promise的异步行为
- 控制状态的最终转换。(通过两个函数参数实现的resolve()和 rejected())
resolve() 把状态切换为resolved, rejected()把状态切换为 rejected,并抛出错误,哪个被调用,状态转换都是不可撤销的。
Promise.resolve()
静态方法,实例化一个解决的期约(以下两者相同)
let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();
这个解决的期约的值对应着传给 Promise.resolve()的第一个参数。可以把任何值都转换为一个期约
setTimeout(console.log, 0, Promise.resolve());
// Promise <resolved>: undefined
setTimeout(console.log, 0, Promise.resolve(3));
// Promise <resolved>: 3
// 多余的参数会忽略
setTimeout(console.log, 0, Promise.resolve(4, 5, 6));
// Promise <resolved>: 4
Promise.rejected()
实例化一个拒绝的期约并抛出一个异步错误(这个错误是不能通过try/catch捕获,只能通过拒绝处理程序捕获)
let p1 = new Promise((resolve, reject) => reject());
let p2 = Promise.reject();
这个拒绝的期约的理由就是传给 Promise.reject()的第一个参数。这个参数也会传给后续的拒绝处理程序
let p = Promise.reject(3);
setTimeout(console.log, 0, p); // Promise <rejected>: 3
p.then(null, (e) => setTimeout(console.log, 0, e)); // 3
Promise.prototype.then()
Promise.prototype.then()是为期约实例添加处理程序的主要方法。这个 then()方法接收最多两个参数:onResolved 处理程序和 onRejected 处理程序。这两个参数都是可选的,如果提供的话, 则会在期约分别进入“兑现”和“拒绝”状态时执行。
// 不传 onResolved 处理程序的规范写法
p2.then(null, () => onRejected('p2'));
Promise.prototype.catch()
Promise.prototype.catch()方法用于给期约添加拒绝处理程序。这个方法只接收一个参数:onRejected 处理程序,语法糖,等同于Promise.prototype. then(null, onRejected)
let p = Promise.reject();
let onRejected = function(e) {
setTimeout(console.log, 0, 'rejected');
};
// 这两种添加拒绝处理程序的方式是一样的:
p.then(null, onRejected); // rejected
p.catch(onRejected); // rejected
Promise.prototype.finally()
用于给期约添加 onFinally 处理程序,这个处理程序在期约转换为解决拒绝状态时都会执行。这个方法可以避免onResolved 和 onRejected 处理程序中出现冗余代码。但 onFinally 处理程序没有 法知道期约的状态是解决还是拒绝,所以这个方法主要用来添加清理代码。
期约连锁
// 串行化异步函数
let p1 = new Promise((resolve, reject) => {
console.log('p1 executor');
setTimeout(resolve, 1000);
});
p1.then(() => new Promise((resolve, reject) => {
console.log('p2 executor');
setTimeout(resolve, 1000);
}))
.then(() => new Promise((resolve, reject) => {
console.log('p3 executor');
setTimeout(resolve, 1000);
}))
.then(() => new Promise((resolve, reject) => {
console.log('p4 executor');
setTimeout(resolve, 1000);
}));
// p1 executor(1 秒后)
// p2 executor(2 秒后)
// p3 executor(3 秒后)
// p4 executor(4 秒后)
// 提取成工厂函数
function delayedResolve(str) {
return new Promise((resolve, reject) => {
console.log(str);
setTimeout(resolve, 1000);
});
}
delayedResolve('p1 executor')
.then(() => delayedResolve('p2 executor'))
.then(() => delayedResolve('p3 executor'))
.then(() => delayedResolve('p4 executor'))
// p1 executor(1 秒后)
// p2 executor(2 秒后)
// p3 executor(3 秒后)
// p4 executor(4 秒后)
每个后续的处理程序都会等待前一个期约解决,然后实例化一个新期约并返回它。
let p = new Promise((resolve, reject) => {
console.log('initial promise rejects');
reject();
});
p.catch(() => console.log('reject handler'))
.then(() => console.log('resolve handler'))
.finally(() => console.log('finally handler'));
// initial promise rejects
// reject handler
// resolve handler
// finally handler
多期约实例组合的静态方法
Promise.all()
创建的期约会在一组期约全部解决之后再解决。
合成的期约只会在每个包含的期约都解决之后才解决:
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.race()
返回一个包装期约,是一组集合中最先解决或拒绝的期约的镜像。Promise.race()不会对解决或拒绝的期约区别对待。无论是解决还是拒绝,只要是第一个落定的期约,Promise.race()就会包装其解决值或拒绝理由并返回新期约。
// 迭代顺序决定了落定顺序
let p3 = Promise.race([
Promise.resolve(5),
Promise.resolve(6),
Promise.resolve(7)]);
setTimeout(console.log, 0, p3); // Promise <resolved>: 5