Promise那些事儿

73 阅读5分钟

一、入门

1、回调地狱(厄运金字塔)

“基于回调”的异步编程风格,对于一个或两个嵌套的调用看起来还不错,但对于一个接一个的多个异步行为,就会进入我们通常所应竭力避免的“回调地狱”。

2、promise(承诺)

2.1、promise对象

Promise 是将“生产者代码(producing code)”和“消费者代码(consuming code)”连接在一起的一个特殊的 JavaScript 对象。

基础构造语法如下:

let promise = new Promise(function(resolve, reject) { // executor(生产者代码) });

promise对象具有以下内部属性:

state —— 最初是 "pending",然后在 resolve 被调用时变为 "fulfilled",或者在 reject 被调用时变为 "rejected"

result —— 最初是 undefined,然后在 resolve(value) 被调用时变为 value,或者在 reject(error) 被调用时变为 error

executor调用resolve和reject来更新promise的状态和结果,一个 resolved 或 rejected 的 promise 都会被称为 “settled”。

2.2、 .then与.catch

我们通过使用 .then 和 .catch 方法注册消费函数

•.then第一个参数在resolved后执行、第二个参数在rejected后执行

•允许对一个promise对象添加多个.then处理程序

•.catch(errorHandlingFunction)相当于.then(null, errorHandlingFunction)

•.catch() 方法来捕获链中任何环节的错误

 // .then
 promise1.then( 
    function(result) { /* handle a successful result */ }, 
    function(error) { /* handle an error */ } 
 );
 promise1.then((result) => {/* another handler */});
 // .catch
 promise2.catch(function(error) {});
 promise2.then(null, function(error){});

2.3、 .finally

通常用来执行常规清理动作

•.finally(f) 在promise settled 时执行f:无论成功还是失败。

•.finally先于.then或.catch执行

new Promise((resolve, reject) => {
  resolve("成功")
})
.finally(() => alert("Promise settled")) // 先触发
.then(result => alert(result)); // <-- .then 显示 "成功"

3、async和await

promise 的处理程序 .then、.catch 和 .finally 都是异步的。

即便一个 promise 立即被 resolve,下面的代码也会先于处理程序执行。

这个涉及到“微任务队列(microtask queue)”,当一个 promise 准备就绪时,它的 .then/catch/finally 处理程序就会被放入队列中,但是它们不会立即被执行。当 JavaScript 引擎执行完当前的代码,它会从队列中获取任务并执行它。

•队列(queue)是先进先出的:首先进入队列的任务会首先运行。

•只有在 JavaScript 引擎中没有其它任务在运行时,才开始执行任务队列中的任务。

 let promise = Promise.resolve(); 
 promise.then(() => alert("promise done!")); 
 alert("code finished"); // 这个 alert 先显示
 
 Promise.resolve()
    .then(() => alert("promise done!"))
    .then(() => alert("code finished"));
 // 按照预期顺序显示

将后续代码都放到处理程序中,可以达到预期执行效果,但是这种写法有时候不那么合适,async和await关键字提供了更优雅的写法。

•async——确保了函数返回一个 promise

•await——让 JavaScript 引擎等待直到 promise 完成(settle)并返回结果

async function f() { 
    let promise = new Promise((resolve, reject) => { 
        setTimeout(() => resolve("done!"), 1000) 
    });
    let result = await promise; // 等待,直到promise settle
    alert(result); // done!
}
f(); 

如果promise 被 reject,await promise将 throw 这个 error。正常情况下,promise 可能需要一点时间后才 reject,我们通常使用try..catch 而不是 .catch来捕获 error。

async function f() {
	try {
		let response = await fetch('http://no-such-url');
	} catch(err) {
		alert(err); // TypeError: failed to fetch
	}
}
f();

二、进阶

1、promise链

Promise 可以通过 .then() 方法链式调用,每个 .then() 都返回一个新的 promise,可以用来进行一系列的异步操作。

doSomething()
  .then(result => doSomethingElse(result))
  .then(newResult => doThirdThing(newResult))
  .then(finalResult => {
    console.log(`Got the final result: ${finalResult}`);
  })
  .catch(failureCallback);

在这个链中,如果任何一个 .then() 抛出错误或者返回一个被拒绝(rejected)的 promise,控制流将跳转到 .catch()。

2、Promise API

Promise 类有 6 种静态方法,其中最常用的是Promise.all(),我们着重说一下。

2.1、Promise.all()

Promise.all 接受一个可迭代对象(通常是一个数组项为 promise 的数组),当所有给定的 promise 都 resolve 时,其结果数组将成为返回的新 promise 的结果。而且,数组中元素的顺序与其在源 promise 中的顺序相同。

 Promise.all([ 
    new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1 
    new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2 
    new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3 
 ]).then(alert); // 1,2,3

如果其中一个 promise 被 reject,Promise.all 就会立即被 reject,完全忽略列表中其他的 promise。它们的结果也被忽略。

Promise.all([ 
    new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
    new Promise((resolve, reject) => setTimeout(() => reject(new Error("我失败了")), 2000)),   
    new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: 我失败了

被 reject 的 error 成为了整个 Promise.all 的结果。

如果我们想要获得所有promises的结果,无论成功还是失败,那么就要用到下面的api

2.2、Promise.allSettled()

Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何。结果数组具有:

{status:"fulfilled", value:result} 对于成功的响应,

{status:"rejected", reason:error} 对于 error。

Promise.allSettled([ 
    new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), 
    new Promise((resolve, reject) => setTimeout(() => reject(new Error("我失败了")), 2000)),   
    new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) 
]).then(results => {
    results.forEach((result, num) => {
      if (result.status == "fulfilled") {
        console.log(result.status, result.value);
      }
      if (result.status == "rejected") {
        console.log(result.status, result.reason);
      }
    });
  });
  // 打印结果
  // fulfilled 1
  // rejected Error: 我失败了
  // fulfilled 3

2.3、其他api

Promise.race(promises) —— 等待第一个 settle 的 promise,并将其 result/error 作为结果返回。

Promise.any(promises)—— 等待第一个 fulfilled 的 promise,并将其结果作为结果返回。如果所有 promise 都 rejected,Promise.any 则会抛出 AggregateError 错误类型的 error 实例(一个特殊的 error 对象,在其 errors 属性中存储着所有 promise error)。

Promise.resolve(value) —— 使用给定 value 创建一个 resolved 的 promise。

Promise.reject(error) —— 使用给定 error 创建一个 rejected 的 promise。

3、async与await特殊场景

3.1、async/await 和 Promise.all 一起使用

当我们需要同时等待多个 promise 时,我们可以用 Promise.all 把它们包装起来,然后使用 await:

async function f() {
    let results = await Promise.all([ 
        new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1  
        new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2  
        new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3  
    ]);
    console.log(results); 
};
f(); // [1, 2, 3]

3.2、await兼容非promise

await 允许我们对那些可能不是一个 promise但具有可调用的 then 方法的对象使用。

await 会等待对象.then两个内置函数resolve和reject中的某个被调用,然后使用得到的结果继续执行后续任务。

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    setTimeout(() => resolve(this.num * 2), 1000);
  }
}
async function f() {
  // 等待 1 秒,之后 result 变为 2
  let result = await new Thenable(1); 
  alert(result); // 2
}
f();

参考文档: zh.javascript.info/async

写在末尾:能力一般,水平有限,本文可能存在纰漏或错误,如有问题欢迎指正🙏