期约(Promise)-第11章小结

123 阅读4分钟

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三个状态

  1. 待定pending (未开始或者执行中)
  2. 兑现、解决resolved(成功完成)
  3. 拒绝 rejected(没有成功完成)

通过执行函数控制期约状态

Promise状态是私有的,所以只能在内部操作。内部操作在Promise的执行器函数中完成。 执行器函数的两大职责:

  1. 初始化Promise的异步行为
  2. 控制状态的最终转换。(通过两个函数参数实现的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