面试 JS 篇 - Promise 相关面试题

lxf2023-12-15 23:00:01

总结一下之前面试遇到的 Promise 相关的问题:

1. promise 的特点

  • 对象的状态不会受外界环境的影响。Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fufilled(已成功)、rejected(已失败),只有异步操作的结果才能决定最后的状态。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。 状态的改变只能由 pending -> fulfilled/resolved 和 pending -> rejected。

2. promise 原理

  • Promise 对象是一个构造函数,用来生成 Promise 实例,这个实例一旦创建就会立即执行;
  • 实例中接收一个执行函数作为参数,这个执行函数有两个函数类型形参(resolve 和 reject);
    • resolved 函数的作用是将 Promie 对象的状态从“未完成”到“成功”,在异步函数成功时调用,并将异步操作的结果作为参数传递出去;
    • rejected 函数的作用是将 Promise 对象的状态从“未完成”到“失败”,在异步函数失败时调用,并将异步操作报出的错误作为参数传递出去。
  • Promise 实例生成后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数。

3. 如何同时请求三个 url 之后再执行接下来的程序

promise.all().then()

4. promise.all 和 promise.allSettled 的区别

4.1 promise.all

  • 接收一个 PromiseList;
  • 多个 Promise 任务同时执行;
  • 如果全部成功则以数组的方式返回所有 Promise 任务的执行结果
  • 如果有一个 Promise rejected,则只返回 rejected 任务的结果
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});
const promise4 = Promise.reject(new Error('error'));

Promise.all([promise1, promise2, promise3])
    .then((values) => {
        console.log(values); // [3, 42, "foo"]
    });

Promise.all([promise1, promise2, promise3, promise4])
    .catch(err => {
        console.log(err); // Error
    });

4.2 promise.allSettled

  • 接收一个 PromiseList;
  • 多个 Promise 任务同时执行;
  • 不论 PromiseList 里面的任意一个 promise 是 fulfilled 还是 rejected,都返回一个数组对象,每个对象表示对应的 promise 结果。
const p1 = Promise.resolve(33);
const p2 = new Promise((resolve) => setTimeout(resolve, 0, 66));
const p3 = 99;
const p4 = Promise.reject(new Error("an error"));

Promise.allSettled([p1, p2, p3, p4]).then((values) => console.log(values)); 

// [
//   { status: 'fulfilled', value: 33 },
//   { status: 'fulfilled', value: 66 },
//   { status: 'fulfilled', value: 99 },
//   { status: 'rejected', reason: Error: an error }
// ]

5. promise.race 和 promise.any 的区别

5.1 promise.race

  • 接收一个 promiseList;
  • 多个 Promise 任务同时执行;
  • 返回最先执行结束的 Promise 任务的结果,不管这个 Promise 是成功还是失败
const p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000, "promise1"));
const p2 = new Promise((resolve, reject) => setTimeout(reject, 1000, "error promise2 "));

Promise.race([p1, p2]).catch(err => {
  console.log('err', err);  // err error promise2 
})

const p3 = new Promise((resolve, reject) => setTimeout(reject, 3000, "error promise3"));
const p4 = new Promise((resolve, reject) => setTimeout(resolve, 1000, "promise4"));

Promise.race([p3, p4]).then(res => {
  console.log('res', res);  // res promise4
});

5.2 promise.any

  • 接收一个 promiseList;
  • 多个 Promise 任务同时执行;
  • 只要一个 Promise 执行成功,则返回该执行成功的promise 结果;如果全都失败,则返回AggregateError 实例,这是 Error 的子类,用于把单一的错误集合在一起
const p2 = new Promise((resolve, reject) => setTimeout(reject, 2000, "error promise2 "));
const p3 = new Promise((resolve, reject) => setTimeout(reject, 3000, "error promise3"));
const p4 = new Promise((resolve, reject) => setTimeout(resolve, 4000, "promise4"));

Promise.any([p2, p3, p4]).then((first) => {
    console.log(first);  // promise4
});

Promise.any([p2, p3]).catch(err => {
    console.log(err); // AggregateError: All promises were rejected
});

6. try...catch... 可以捕获 Promise 异常吗?

不能,try...catch只能捕获同步异常,对于异步异常不能捕获。

promise中的异常可以用 promise.catch 进行处理。

// 方法1
async function run() {
    try {
        await Promise.reject(new Error("Oops!"));
    } catch (error) {
        console.log(error.message); // "Oops!"
    }
}
run();

// 方法2
Promise.reject(new Error("Oops!")).catch(err => console.log(err));  // Error: Oops!

7.代码

7.1 实现 Promise.all

Promise.all = function (promiseList = []) {
  const p1 = new Promise((resolve, reject) => {
    const result = [];  // 存储 promiseList 所有的结果
    const length = promiseList.length;
    let resolvedCount = 0;

    promiseList.forEach(p => {
      Promise.resolve(p).then(data => {
        result.push(data);
        resolvedCount++;
        if (resolvedCount === length) {  // 已经遍历到了最后一个 promise
          resolve(result);
        }
      }).catch(err => {
        reject(err);
      })
    })
  });
  
  return p1;
}
问题1:为什么不用用 index,反而单独定义了一个 resolvedCount 变量?

因为有的 promise 执行的慢,有的 promise 执行的快,会出现执行顺序被打乱的问题。

问题2:如果 promiseList 中有常量怎么办?

利用 Promise.resolve 的特性,用这个方法包裹的参数,无论是什么类型,都会返回一个 promise。

7.2 实现红黄绿灯交替闪烁,可查看另一篇文章

7.3 输出以下代码的执行结果①

Promise.resolve().then(() => {
  console.log(0);
  return Promise.resolve(4);
}).then(res => {
  console.log(res);
});

Promise.resolve().then(() => {
  console.log(1);
}).then(() => {
  console.log(2);
}).then(() => {
  console.log(3);
}).then(() => {
  console.log(5);
}).then(() => {
  console.log(6);
});

这道题中考察到了两个知识点:

  1. then 函数会交替执行

    如果有多个 fulfilled 状态的 promise 实例,同时执行 then 链式调用,那么每个 promise 的 then 函数会交替执行,这是编译器的优化,防止一个 promise 占据太久时间

  2. 慢“两拍”

    then 中返回一个 promise 实例的时候,会出现慢两拍的情况:

    • 第一拍:promise 需要由 pending 变成 fulfilled
    • 第二拍:then 函数挂载到 MicroTaskQueue(参考 事件循环 event loop)
  3. 结果

    综上,最终的执行结果是 0 1 2 3 4 5 6

7.4 输出以下代码的执行结果②

Promise.resolve().then(() => {
  console.log(1);
}).catch(() => {
  console.log(2);
}).then(() => {
  console.log(3);
})

输出结果:1、3、resolved状态的 Promise

7.5 输出以下代码的执行结果③

Promise.resolve().then(() => { // rejected
  console.log(1);
  throw new Error('error')
}).catch(() => {   // resolved
  console.log(2);
}).then(() => {  // resolved
  console.log(3);
})

输出结果:1、2、3、resolved状态的 Promise

7.6 输出以下代码的执行结果④

Promise.resolve().then(() => { // rejected
  console.log(1);
  throw new Error('error')
}).catch(() => {   // resolved
  console.log(2);
}).catch(() => {
  console.log(3);
})

输出结果:1、2、resolved状态的 Promise

如果后续有遇到,会持续更新,如果有问题,也欢迎一起讨论⌛️~

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!