一篇文章带你拿下在面试中关于Promise的考点

126 阅读5分钟

一、Promise是什么?

Promsie 是异步编程的一种解决方案,本质上是一个函数返回的对象。它的构造函数是同步执行的,但 then 方法是异步的。所以Promise创建后里面的函数会立即执行,而构造函数中的resolve和reject只有在第一次执行时才有效。也就是说Promise的状态一旦改变就不能再变。

二、Promise的作用

Promise主要是用来解决回调地狱,通过.then方法使得代码成链式调用,方便以后的维护和使用。

1.回调地狱

回调地狱(Callback Hell)也被称为“金字塔之Doom”,是指在使用异步编程时,由于多个回调函数的嵌套使用而导致代码变得复杂、难以阅读和维护。

回调地狱的特点:

  • 深度嵌套: 因为异步操作的数量不断增加,回调函数不断嵌套,形成一个向右扩展的倒金字塔形状。
  • 代码可读性差: 随着嵌套的函数越来越多,降低了代码的可读性和维护性。
  • 错误处理困难: 因为每个回调函数都有可能发生错误,增加了出错的可能性。

2.链式调用

Promise的链式调用是通过 then 方法,将多个异步操作串联起来,并且每个 then 方法都可以返回一个新的 Promise,从而实现链式调用。

Promise链式调用的基本原理

  1. 在每次调用 .then 方法后都会返回一个新的 Promise。
  2. 如果是返回一个普通值,那么下一个 then 方法会把这个值作为参数。
  3. 如果返回一个 Promise ,那么下一个 then 方法会等待这个 Promise 被解决(fulfilled或rejected)后再执行。

错误处理

在链式调用中,任何一步出错都会跳过后续所有的 then 方法,直接进入最近的 catch 方法来处理错误。所以,在 then 方法中如果出现了错误,可以通过抛出异常或返回一个被拒绝的 Promise 来触发错误处理逻辑。

三、Promise的状态

Promise 总共有三种状态,且一定处于某种状态之中:

  • 待定(pending):初始状态,既没有成功,也没有失败。但一旦确定了状态,就永远不会变。
  • 成功(fulfilled):意味着操作成功。
  • 拒绝(rejected):意味着操作失败。

状态转换

  • pending -> fulfilled: 当异步操作成功时,Promise 的状态会从 Pending 变成 fulfilled,并传递一个值给处理函数。
  • pending ->rejected : 当异步操作失败后,Promise 的状态会变成 rejected,并抛出原因给处理函数。

四、Promise的并发

Promise 提供了四种静态方法来处理异步任务的并发:

  • Promise.all(): 接受一个 Promsie 实例数组,并返回新的 Promise 。当所有的 Promsie 完成后才会被解决,并将所有Promsie 的结果作为一个数组传递给 .then() 方法中的回调函数。如果任意一个 Promise 被拒绝后,那么整个 Promise.all() 就会被立刻拒绝,并抛出错误信息。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(values => {
  console.log(values); // 输出: [3, 42, "foo"]
}).catch(error => {
  console.error(error);
  • Promise.allSettled(): 功能与 Promise.all()相似,只是不管是否成功或失败,都会返回一个对象数组。每个对象都包含属性(fulfilled 或 rejected)和相应的值或原因。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'Error'));
const promise3 = 42;

Promise.allSettled([promise1, promise2, promise3]).then(results => {
  results.forEach(result => {
    if (result.status === 'fulfilled') {
      console.log(`Fulfilled: ${result.value}`);
    } else {
      console.error(`Rejected: ${result.reason}`);
    }
  });
  // 输出:
  // Fulfilled: 3
  // Rejected: Error
  // Fulfilled: 42
});
  • Promise.race(): 接受一个 Promsie 实例数组,并返回新的 Promise 。而这个新的 Promsie 会在第一个输入的 Promise 被解决时立即解决,并返回结果或错误信息。
const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'one'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'two'));

Promise.race([promise1, promise2]).then(value => {
  console.log(value); // 输出: "two"
}).catch(error => {
  console.error(error);
});
  • Promise.any(): 和 Promise.race()类似,新返回的 Promise 会在任意一个输入的 Promsie 完成后立即解决,只有所有输入的 Promsie 被拒绝后才会被拒绝。是因为 AggregateError 对象,包含了所有拒绝的原因。
const promise1 = new Promise((_, reject) => setTimeout(reject, 100, 'one'));
const promise2 = new Promise((_, reject) => setTimeout(reject, 200, 'two'));
const promise3 = Promise.resolve('three');

Promise.any([promise1, promise2, promise3]).then(value => {
  console.log(value); // 输出: "three"
}).catch(error => {
  if (error instanceof AggregateError) {
    console.error("All promises were rejected", error.errors);
  }
});

要注意的是,JS本质上是单线程的,所以无论何时只有一个任务会被执行,是因为控制权不断在Promise 之间切换,才使得Promsie的执行看起来像并发的。

五、Promise的静态方法

Promise 提供了几个静态方法来解决异步操作。其中Promise.all()Promise.allSettled()Promise.race()Promise.any()已经介绍过了,现在让我们看看剩下的几个。

  • Promise.resolve(): 将传入的值转换成 Promise,如果本身就是 Promise 则直接返回该 Promise;如果值是一个 thenable(有.then 方法的对象),那会将其包装成 Promsie 返回;否则返回以该值为结果的新 Promise。
// 返回一个已经 fulfilled 的 Promise
Promise.resolve('Success').then(value => {
 console.log(value); // 输出: "Success"
});

// 处理 thenable 对象
const thenable = {
 then: function(resolve, reject) {
   resolve('Resolved');
 }
};
Promise.resolve(thenable).then(value => {
 console.log(value); // 输出: "Resolved"
});
  • Promise.reject(): 会返回一个已经被拒绝的 Promise,并返回原因。
Promise.reject(new Error('Failed')).catch(error => {
  console.error(error.message); // 输出: "Failed"
});

六、Promise的构造函数

Promsie 的构造函数是创建 Promise 实例的主要方法。它把执行器(executor)函数作为参数,这个执行器函数是会立即执行的,通常用于启动一步操作。

执行器函数有两个参数:resolve 和 reject,这些都是函数。

  • resolve: 异步操作成功执行时调用,传入的数据作为 promise 的结果。
  • reject: 异步操作失败时调用,把传入的原因作为 Promsie 的拒绝原因。

构造函数说明

首先,用 new 创建一个 Promise 实例,其状态是 pending(待定)。Promise 在创建后就会立即执行,执行器函数会启动异步操作,并根据结果调用 resolve 或 reject。调用 resolve(value) ,Promise 状态会变成 fulfilled(已实现),并返回值。调用reject(reason),状态会变成 rejected(已拒绝),并返回原因。

七、总结

这篇文章主要写了 Promise 在面试中的一些考点,涉及具体使用、注意要点和原理性知识,但由于本人掌握有限,要是有什么不足,还请指出。如果觉得不错,还请点赞收藏。