前端 Promise 全解:从原理到面试

重点总结

  1. 定义:Promise 是异步编程的一种解决方案,解决了回调地狱问题,提供了统一的 API。

  2. 状态:它有 Pending、Fulfilled、Rejected 三种状态,且状态不可逆。

  3. 使用

    • 构造函数是同步执行的。

    • .then 返回新 Promise,支持链式调用。

    • .catch 处理错误(冒泡机制)。

  4. 常用 API

    • all(并发,全对才对)。

    • race(赛跑,谁快用谁)。

    • allSettled(不管死活,都要结果)。

    • any(只要有一个活的就行)。

  5. 运行机制 :Promise 的回调是微任务,在同步代码执行完后、宏任务执行前执行,这涉及到了 Event Loop。

  6. 最佳实践:现在项目中主要配合 async/await 使用,使异步代码看起来像同步代码,可读性更强。

1. 什么是 Promise?(核心概念)

一句话定义: Promise 是异步编程的一种解决方案,它是一个对象,代表了一个异步操作的最终完成(或失败)及其结果值。

1.1 为什么要用 Promise?

在 Promise 出现之前,我们处理异步主要靠回调函数(Callback)。当异步操作依赖另一个异步操作时,就会出现"回调地狱"(Callback Hell):

javascript 复制代码
// 噩梦般的回调地狱
doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

Promise 的作用: 将嵌套的回调改为链式调用,解决了回调地狱问题,使代码更具可读性和逻辑性,同时提供了更好的错误处理机制。

1.2 Promise 的三种状态

Promise 是一个状态机,它拥有三种状态:

  1. Pending(进行中):初始状态,既没有被兑现,也没有被拒绝。

  2. Fulfilled(已成功/Resolved):意味着操作成功完成。

  3. Rejected(已失败):意味着操作失败。

重要特性:状态不可逆

一旦状态从 Pending 变为 Fulfilled 或 Rejected,状态就固定了,不会再发生改变。这被称为 "Settled"(已决议)。

2. Promise 的基本使用与实例方法

2.1 构造函数 (Executor)

javascript 复制代码
const p = new Promise((resolve, reject) => {
  // Executor 执行器函数,是【同步执行】的!
  console.log('1. Executor runs immediately');
  
  // 模拟异步
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('Success Data'); // 状态变成 Fulfilled
    } else {
      reject('Error Reason');  // 状态变成 Rejected
    }
  }, 1000);
});

面试考点: new Promise(fn) 中的 fn 是立即同步执行的。

2.2 .then(onFulfilled, onRejected)

  • 作用:接收 Promise 成功或失败的结果。

  • 返回值 :返回一个新的 Promise(这是链式调用的关键)。

  • 特性

    • 如果回调函数返回一个普通值,新 Promise 状态为 Fulfilled,值为该返回值。

    • 如果回调函数抛出错误,新 Promise 状态为 Rejected。

    • 如果回调函数返回一个 Promise,新 Promise 将等待这个 Promise 的状态。

    • 值穿透:如果 .then() 没传回调函数,值会原样向后传递。

2.3 .catch(onRejected)

  • 作用:捕获 Promise 链中的错误。

  • 本质:它只是 .then(null, onRejected) 的语法糖。

  • 冒泡性:错误会沿着链一直向下传递,直到被最近的 catch 捕获。

2.4 .finally(onFinally)

  • 作用:无论 Promise 是成功还是失败,都会执行。

  • 场景:关闭 Loading 动画、关闭数据库连接等清理工作。

  • 注意:finally 不接收任何参数,它也不知道前面的状态是啥。它返回的 Promise 通常会延续上一个 Promise 的结果(除非 finally 里面抛错)。


3. Promise 静态方法 (API 详解)

这是面试中最常考察应用场景的部分。

3.1 Promise.resolve(value)

  • 作用:将现有对象转为 Promise 对象(状态为 Fulfilled)。

  • 场景:当你需要一个确定的成功结果进行后续链式调用时。

3.2 Promise.reject(reason)

  • 作用:返回一个状态为 Rejected 的 Promise 实例。

3.3 Promise.all([p1, p2, p3])

  • 逻辑"并发执行,全对才对,一错全错"

  • 输入:一个 Promise 数组(Iterator)。

  • 输出

    • 成功:当所有 Promise 都成功时,返回一个数组,包含所有结果(顺序与输入一致)。

    • 失败:只要有一个失败,立即返回那个失败的原因(短路效应)。

  • 场景:页面加载时,同时请求用户信息、菜单列表、系统配置,三个都拿到才能渲染页面。

3.4 Promise.race([p1, p2, p3])

  • 逻辑"赛跑机制,谁快听谁的"

  • 输出:返回第一个改变状态(无论是成功还是失败)的 Promise 的结果。

  • 场景

    • 请求超时控制:比如网络请求和 setTimeout 赛跑,如果 5秒没回来,setTimeout 赢了,抛出超时错误。

3.5 Promise.allSettled([p1, p2, p3]) (ES2020)

  • 逻辑"我全都要,不管对错"

  • 输出:等待所有 Promise 都结束(Settled)。返回一个数组,每个对象包含 status ('fulfilled'/'rejected') 和对应的 value 或 reason。

  • 场景:你需要知道所有请求的结果,不关心其中某个是否失败。例如批量上传图片,失败几张没关系,需要知道哪几张成功哪几张失败。

3.6 Promise.any([p1, p2, p3]) (ES2021)

  • 逻辑"只要有一个成就算成功"

  • 输出

    • 只要有一个 Promise 成功,就返回那个成功的。

    • 只有当所有 Promise 都失败时,才返回失败(AggregateError)。

  • 场景:从多个 CDN 节点加载同一张图片,只要有一个加载出来就行。


4. 进阶:Promise 与事件循环 (Event Loop)

这是区分初级和中高级开发者的分水岭。

4.1 微任务 (Microtask)

Promise 的 .then、.catch、.finally 中的回调函数属于 微任务

setTimeout、setInterval 属于 宏任务 (Macrotask)

4.2 执行顺序

  1. 执行同步代码(包括 new Promise 构造函数内部的代码)。

  2. 同步代码执行完,检查并执行所有微任务队列(Promise 回调)。

  3. 渲染 DOM(如果有必要)。

  4. 执行一个宏任务。

  5. 回到步骤 2 循环。

经典面试题:

javascript 复制代码
console.log('1');

setTimeout(() => {
  console.log('2');
}, 0);

Promise.resolve().then(() => {
  console.log('3');
});

new Promise((resolve) => {
  console.log('4');
  resolve();
}).then(() => {
  console.log('5');
});

console.log('6');

答案与解析:

输出顺序:1 -> 4 -> 6 -> 3 -> 5 -> 2

  1. 1: 同步。

  2. setTimeout: 放入宏任务队列。

  3. 3: 放入微任务队列。

  4. 4: new Promise 是同步执行的。

  5. 5: 放入微任务队列。

  6. 6: 同步。

  7. 同步结束,清空微任务:输出 3, 5。

  8. 微任务清空,执行宏任务:输出 2。


5. 面试常见"手写"题思路

面试官可能会让你手写 Promise.all 或简易版 Promise。

5.1 手写 Promise.all

核心逻辑

  1. 返回一个新的 Promise。

  2. 遍历输入数组。

  3. 用 Promise.resolve 包装每一项(防止数组里有非 Promise 值)。

  4. 维护一个计数器 count 和结果数组 results。

  5. 每成功一个,results[i] = value,count++。

  6. 如果 count === length,调用 resolve(results)。

  7. 只要有一个失败,直接调用 reject(reason)。

javascript 复制代码
Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    let results = [];
    let count = 0;
    if(promises.length === 0) resolve([]);
    
    promises.forEach((p, index) => {
      Promise.resolve(p).then(res => {
        results[index] = res; // 保证结果顺序和输入顺序一致
        count++;
        if (count === promises.length) {
          resolve(results);
        }
      }, err => {
        reject(err);
      })
    })
  })
}

6. Promise 的终极形态:Async/Await

面试必问:Async/Await 和 Promise 的关系?

  • async/await 是 Generator + Promise 的语法糖

  • async 函数返回的一定是一个 Promise。

  • await 后面通常接 Promise,它会暂停 async 函数的执行,等待 Promise 解决,并把结果作为 await 表达式的值。

  • 优势:用同步的代码逻辑写异步代码,彻底解决了 .then 链过长的问题,try...catch 可以同时捕获同步和异步错误。


相关推荐
天意pt14 小时前
Blog-SSR 系统操作手册(v1.0.0)
前端·vue.js·redis·mysql·docker·node.js·express
遗憾随她而去.14 小时前
Webpack5 高级篇(一)
前端
疯狂踩坑人15 小时前
【React 19 尝鲜】第一篇:use和useActionState
前端·react.js
毕设源码-邱学长15 小时前
【开题答辩全过程】以 基于VUE的打车系统的设计与实现为例,包含答辩的问题和答案
前端·javascript·vue.js
用户390513321928815 小时前
JS判断空值只知道“||”?不如来试试这个操作符
前端·javascript
海云前端115 小时前
前端面试必问 asyncawait 到底要不要加 trycatch 90% 人踩坑 求职加分技巧揭秘
前端
wuk99816 小时前
梁非线性动力学方程MATLAB编程实现
前端·javascript·matlab
XiaoYu200216 小时前
第11章 LangChain
前端·javascript·langchain
霉运全滚蛋好运围着转17 小时前
启动 Taro 4 项目报错:Error: The specified module could not be found.
前端