Promise 从入门到精通:彻底解决前端异步回调问题!!!

Promise 从入门到精通:彻底解决前端异步回调问题

🚀 一、Promise 核心介绍

1. 什么是 Promise?

Promise 是 ES6(ECMAScript 2015)引入的异步编程解决方案 ,是一个用于封装异步操作并管理其结果的对象。它解决了传统异步编程中回调地狱(Callback Hell) 的嵌套问题,让异步代码的逻辑更清晰、更易维护,同时提供了统一的异步错误处理机制,是现代前端异步编程的基础(async/await 正是基于 Promise 实现的语法糖)。

简单来说:Promise 就像一个异步操作的"承诺" ------ 异步操作执行前,它处于等待状态;异步操作完成后,它会兑现"承诺"(返回成功结果)或拒绝"承诺"(返回失败原因),且这个结果一旦确定就不可修改

2. 核心特性

  • 状态不可逆:Promise 有且仅有三种状态,状态一旦改变,就会永久保持该状态,不会再发生变化;
  • 统一的异步范式:将不同类型的异步操作(AJAX、定时器、文件操作等)封装为统一的 Promise 接口,解决异步操作格式不统一的问题;
  • 链式调用 :支持 .then().catch().finally() 链式调用,替代多层回调嵌套,让异步代码线性化;
  • 集中错误处理 :支持全局错误捕获,一个 .catch() 可捕获链式调用中所有前置操作的错误,避免单独处理每个异步操作的异常;
  • 非阻塞执行:Promise 封装的异步操作不会阻塞浏览器主线程,保证页面交互的流畅性;
  • 一次性执行:Promise 内部的异步操作一旦执行,就会完成整个流程,不会被重复触发。

3. 解决的核心问题:回调地狱

传统异步编程依赖回调函数,当多个异步操作存在顺序依赖 (后一个操作依赖前一个操作的结果)时,会出现多层回调嵌套,形成回调地狱 ,代码表现为「层层缩进的金字塔结构」,存在可读性差、维护困难、错误无法统一捕获等问题。

回调地狱示例(定时器嵌套)

javascript 复制代码
// 需求:依次执行三个异步操作,后一个操作等待前一个完成
setTimeout(() => {
  console.log('第一步操作完成');
  setTimeout(() => {
    console.log('第二步操作完成');
    setTimeout(() => {
      console.log('第三步操作完成');
      // 更多嵌套...
    }, 1000);
  }, 1000);
}, 1000);

Promise 改造后(链式调用,无嵌套)

javascript 复制代码
// 封装Promise版定时器
function delay(time) {
  return new Promise(resolve => setTimeout(resolve, time));
}

// 线性链式调用,逻辑清晰
delay(1000)
  .then(() => console.log('第一步操作完成'))
  .then(() => delay(1000))
  .then(() => console.log('第二步操作完成'))
  .then(() => delay(1000))
  .then(() => console.log('第三步操作完成'));

对比可见:Promise 彻底摆脱了回调嵌套,让异步代码的执行流程与同步代码一致,大幅提升了代码的可读性和可维护性。

4. Promise 与传统异步方案的对比

特性 Promise 传统回调函数 async/await
代码结构 链式调用,无嵌套 多层嵌套,回调地狱 同步化写法,最简洁
错误处理 统一 .catch() 捕获 每个回调单独处理错误 try/catch 捕获,更符合直觉
异步流程控制 原生支持(.then 链式) 手动嵌套控制 同步流程控制(基于Promise)
学习成本 中等 低(但复杂场景易出错) 高(需先掌握Promise)
依赖关系 无(ES6原生) 依赖Promise(ES7)
适用场景 简单至复杂异步流程 简单异步操作(无依赖) 复杂异步流程、多请求依赖

核心结论 :async/await 是 Promise 的语法糖,传统回调是基础,Promise 是现代前端异步编程的核心桥梁

🎯 二、Promise 核心概念

1. 三种状态与状态转换

Promise 的核心是状态管理 ,其一生仅有三种状态,且状态转换不可逆、仅能发生一次,这是 Promise 解决异步不确定性的关键。

三种基础状态
  1. pending(进行中):初始状态,异步操作尚未完成,此时既未成功也未失败;
  2. fulfilled(已成功):异步操作顺利完成,Promise 兑现承诺,返回成功结果;
  3. rejected(已失败):异步操作执行失败,Promise 拒绝承诺,返回失败原因(错误对象)。
唯一的两种状态转换路径

Promise 只能从初始状态向最终状态转换,且转换后状态永久固定,无法反向转换,也无法在成功/失败之间切换

  • 路径1:pendingfulfilled(异步操作成功,调用 resolve() 触发);
  • 路径2:pendingrejected(异步操作失败,调用 reject() 触发)。

核心注意 :一旦状态变为 fulfilledrejected,就会成为定型状态(settled) ,后续再调用 resolve()reject() 均无效。

2. 两个核心回调函数

Promise 构造函数接收一个执行器函数(executor) ,该函数会在 Promise 创建时立即同步执行,且接收两个内置的核心回调函数作为参数,用于手动触发状态转换:

  1. resolve(result) :将 Promise 状态从 pending 转为 fulfilled,并将异步操作的成功结果传递出去(result 可为任意类型:基本类型、对象、数组,甚至另一个 Promise);
  2. reject(reason) :将 Promise 状态从 pending 转为 rejected,并将异步操作的失败原因传递出去(reason 通常为 Error 对象,便于后续错误捕获和栈追踪)。

核心注意 :执行器函数是同步执行 的,内部的异步操作是异步执行的,这是容易混淆的关键点,示例:

javascript 复制代码
const p = new Promise((resolve, reject) => {
  console.log('Promise 执行器函数:同步执行'); // 立即输出
  setTimeout(() => {
    console.log('异步操作:异步执行'); // 1秒后输出
    resolve('异步操作成功');
  }, 1000);
});
console.log('Promise 创建完成'); // 执行器后立即输出

输出顺序:Promise 执行器函数 → Promise 创建完成 → 异步操作:异步执行。

3. 定型状态(Settled)

定型状态是 Promise 的最终状态统称 ,指 Promise 已完成状态转换,不再处于 pending 状态,包含两种情况:

  • fulfilled(已成功)是定型状态;
  • rejected(已失败)也是定型状态。

后续的 .then().catch().finally() 方法,本质都是监听 Promise 的定型状态,一旦状态定型,就会执行对应的回调函数。

📁 三、Promise 实例的核心方法

Promise 实例提供了 .then().catch().finally() 三个核心原型方法,均支持链式调用 (核心原因:每个方法执行后都会返回一个新的 Promise 实例,而非原实例),这是解决回调地狱的关键。

所有方法的回调函数都会被加入微任务队列,在浏览器主线程同步代码执行完成后、宏任务执行前执行(Promise 属于微任务,这是事件循环的重要知识点)。

1. then(onFulfilled, onRejected):处理成功/失败结果

.then() 是 Promise 最核心的方法,用于监听 Promise 的定型状态,接收两个可选的回调参数:

  • onFulfilled(result) :可选,Promise 状态为 fulfilled 时执行,参数 resultresolve() 传递的成功结果;
  • onRejected(reason) :可选,Promise 状态为 rejected 时执行,参数 reasonreject() 传递的失败原因。
核心特性
  1. 链式调用基础.then() 执行后返回新的 Promise 实例,新实例的状态由当前回调函数的执行结果决定;
  2. 参数可选 :可只传 onFulfilled(仅处理成功),也可只传 onRejected(但更推荐用 .catch() 处理失败);
  3. 值的传递 :若回调函数返回一个普通值(非 Promise、非抛出错误),新 Promise 会以 fulfilled 状态将该值传递给下一个 .then()
  4. 错误透传 :若回调函数抛出错误,新 Promise 会以 rejected 状态将错误传递给后续的 .catch().then()onRejected
基础使用示例
javascript 复制代码
const p = new Promise((resolve, reject) => {
  const random = Math.random();
  if (random > 0.5) {
    resolve(`成功:随机数${random.toFixed(2)}`);
  } else {
    reject(new Error(`失败:随机数${random.toFixed(2)}`));
  }
});

// 处理成功和失败
p.then(
  (res) => console.log('then成功回调:', res),
  (err) => console.log('then失败回调:', err.message)
);
链式调用示例(核心)
javascript 复制代码
// 链式调用:依次处理,值的传递
new Promise((resolve) => resolve(1))
  .then((num) => {
    console.log('第一步:', num); // 1
    return num + 1; // 返回普通值,传递给下一个then
  })
  .then((num) => {
    console.log('第二步:', num); // 2
    return new Promise(resolve => setTimeout(() => resolve(num + 1), 1000)); // 返回Promise
  })
  .then((num) => {
    console.log('第三步:', num); // 3(1秒后)
    throw new Error('主动抛出错误'); // 抛出错误,触发后续catch
  })
  .then(
    (num) => console.log('第四步:', num), // 不会执行
    (err) => console.log('then捕获错误:', err.message) // 可捕获,但推荐用catch
  );

2. catch(onRejected):专门处理失败结果

.catch().then(null, onRejected)语法糖 ,专门用于捕获 Promise 链式调用中所有前置操作 的错误(包括 reject() 触发的失败、回调函数中抛出的错误、同步代码错误),是 Promise 错误处理的推荐方式

核心特性
  1. 全局错误捕获 :一个 .catch() 可捕获链式调用中所有前置 .then() 的错误,无需在每个 .then() 中单独处理;
  2. 链式调用.catch() 执行后也会返回新的 Promise 实例,若在 .catch() 中返回普通值,可继续链式调用 .then()
  3. 错误兜底 :若 Promise 链式调用中没有 .catch(),未捕获的错误会触发浏览器的 unhandledrejection 事件,导致控制台报错(生产环境需避免);
  4. 捕获范围 :不仅捕获 Promise 自身的 reject(),还捕获所有回调函数中的同步错误异步错误(如回调中调用未定义的变量)。
基础使用示例
javascript 复制代码
new Promise((resolve, reject) => {
  reject(new Error('异步操作失败'));
})
  .then((res) => console.log('成功:', res)) // 不会执行
  .catch((err) => console.log('catch捕获错误:', err.message)); // 输出:异步操作失败
全局错误捕获示例(链式核心)
javascript 复制代码
// 一个catch捕获所有前置错误
new Promise((resolve) => {
  resolve(1);
})
  .then((num) => {
    console.log(num);
    a++; // 引用未定义变量,同步错误
  })
  .then((num) => console.log(num)) // 不会执行
  .catch((err) => {
    console.log('catch捕获所有错误:', err.message); // 输出:a is not defined
    return 10; // 返回普通值,继续链式调用
  })
  .then((num) => console.log('catch后继续执行:', num)); // 输出:10

3. finally(onFinally):处理最终收尾操作

.finally() 是 ES2018 引入的方法,用于指定无论 Promise 状态是成功还是失败,都会执行的收尾操作,比如关闭加载弹窗、释放资源、取消定时器等。

核心特性
  1. 无参数.finally() 的回调函数不接收任何参数,因为它无需关心 Promise 的执行结果(成功/失败),仅做通用收尾;
  2. 必然执行 :无论 Promise 是 fulfilled 还是 rejected,也无论链式调用中是否有 .catch().finally() 都会执行;
  3. 链式调用.finally() 也会返回新的 Promise 实例,且会透传前置 Promise 的成功结果或失败原因(即不改变原有的结果);
  4. 无返回值影响.finally() 的回调函数返回的值会被忽略,不会影响后续链式调用的参数。
基础使用示例(最常用场景:关闭加载)
javascript 复制代码
// 模拟接口请求
function requestData() {
  return new Promise((resolve, reject) => {
    console.log('显示加载弹窗');
    setTimeout(() => {
      const isSuccess = Math.random() > 0.5;
      if (isSuccess) {
        resolve('请求成功:获取到数据');
      } else {
        reject(new Error('请求失败:网络错误'));
      }
    }, 1000);
  });
}

// 执行请求,finally关闭加载
requestData()
  .then((res) => console.log(res))
  .catch((err) => console.log(err.message))
  .finally(() => {
    console.log('关闭加载弹窗'); // 无论成功/失败,都会执行
  });
透传结果示例
javascript 复制代码
// finally透传成功结果
new Promise((resolve) => resolve('成功数据'))
  .finally(() => {
    console.log('执行finally');
    return 'finally的返回值'; // 会被忽略
  })
  .then((res) => console.log('最终结果:', res)); // 输出:成功数据

// finally透传失败原因
new Promise((reject) => reject(new Error('失败原因')))
  .finally(() => console.log('执行finally'))
  .catch((err) => console.log('最终错误:', err.message)); // 输出:失败原因

🚀 四、Promise 构造函数的静态方法

Promise 构造函数本身提供了多个静态方法 ,用于快速创建 Promise 实例或批量管理多个 Promise 实例,是处理多异步操作流程控制的核心,日常开发中使用频率极高。

1. Promise.resolve(value):快速创建成功的 Promise

用于快速创建一个状态为 fulfilled 的 Promise 实例,等价于 new Promise(resolve => resolve(value)),适合将普通值、非 Promise 异步操作转为 Promise 格式,实现统一的异步接口。

核心特性
  • 若参数 value普通值(基本类型、对象),新 Promise 直接以该值为成功结果;
  • 若参数 value另一个 Promise 实例,则直接返回该实例(状态和结果均透传);
  • 若参数 value具有 then 方法的对象(thenable) ,则会执行其 then 方法,根据 then 方法的执行结果确定新 Promise 的状态。
使用示例
javascript 复制代码
// 1. 传入普通值
const p1 = Promise.resolve(123);
p1.then(res => console.log(p1)); // 123

// 2. 传入Promise实例
const p2 = new Promise(resolve => resolve('原Promise'));
const p3 = Promise.resolve(p2);
console.log(p2 === p3); // true(直接返回原实例)

// 3. 传入thenable对象
const thenable = {
  then(resolve) {
    resolve('thenable执行成功');
  }
};
Promise.resolve(thenable).then(res => console.log(res)); // thenable执行成功

2. Promise.reject(reason):快速创建失败的 Promise

用于快速创建一个状态为 rejected 的 Promise 实例,等价于 new Promise((resolve, reject) => reject(reason)),适合快速抛出异步错误。

核心特性
  • 无论参数 reason 是什么类型(普通值、Promise 实例、thenable 对象),都会直接作为失败原因 传递给 .catch()不会透传 Promise 实例的状态(与 Promise.resolve 不同)。
使用示例
javascript 复制代码
// 1. 传入普通错误
Promise.reject('简单错误').catch(err => console.log(err)); // 简单错误

// 2. 传入Error对象(推荐)
Promise.reject(new Error('标准错误')).catch(err => console.log(err.message)); // 标准错误

// 3. 传入Promise实例(不会透传,直接作为原因)
const p = Promise.resolve('成功的Promise');
Promise.reject(p).catch(err => console.log(err === p)); // true

3. Promise.all(iterable):所有异步操作都成功才成功

核心场景 :处理并行的多个异步操作 ,且所有操作都必须成功 ,才返回所有结果;只要有一个操作失败,立即返回该失败原因(快速失败机制)。

核心特性
  • 参数 iterable:可迭代对象(如数组),每个元素都是 Promise 实例;
  • 成功结果:当所有 Promise 都变为 fulfilled,新 Promise 以 fulfilled 状态返回结果数组,数组顺序与传入的 Promise 顺序一致(与执行完成顺序无关);
  • 失败机制:只要有一个 Promise 变为 rejected,新 Promise 立即以 rejected 状态返回该失败原因,其余未完成的 Promise 仍会执行,但结果会被忽略;
  • 空数组:若传入空数组,会立即成功,返回空数组。
使用示例(并行请求多个接口)
javascript 复制代码
// 模拟3个并行的接口请求
const request1 = Promise.resolve('接口1数据');
const request2 = new Promise(resolve => setTimeout(() => resolve('接口2数据'), 1000));
const request3 = Promise.resolve('接口3数据');

// 所有请求都成功才返回结果
Promise.all([request1, request2, request3])
  .then(res => {
    console.log('所有请求成功:', res); // 输出:['接口1数据', '接口2数据', '接口3数据'](1秒后)
    const [data1, data2, data3] = res; // 按顺序解构
  })
  .catch(err => console.log('有请求失败:', err.message));

// 一个请求失败的情况
const request4 = Promise.reject(new Error('接口4请求失败'));
Promise.all([request1, request4, request3])
  .then(res => console.log(res)) // 不会执行
  .catch(err => console.log(err.message)); // 立即输出:接口4请求失败

4. Promise.race(iterable):第一个完成的异步操作决定结果

核心场景 :处理并行的多个异步操作谁先完成(成功/失败),就取谁的结果,其余未完成的 Promise 仍会执行,但结果会被忽略("赛跑"机制)。

核心特性
  • 参数 iterable:可迭代对象,每个元素都是 Promise 实例;
  • 结果由"第一个定型"的 Promise 决定:无论第一个完成的是 fulfilled 还是 rejected,新 Promise 都会继承其状态和结果;
  • 空数组:若传入空数组,新 Promise 会一直处于 pending 状态,永不定型。
经典使用示例:接口请求超时控制
javascript 复制代码
// 模拟接口请求
function request() {
  return new Promise((resolve) => {
    setTimeout(() => resolve('接口请求成功'), 2000); // 2秒后完成
  });
}

// 模拟超时器(1.5秒后失败)
function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('请求超时:1.5秒未响应')), 1500);
  });
}

// 赛跑:请求和超时器,谁先完成取谁的结果
Promise.race([request(), timeout()])
  .then(res => console.log(res)) // 不会执行,因为超时器先完成
  .catch(err => console.log(err.message)); // 输出:请求超时:1.5秒未响应

5. Promise.allSettled(iterable):等待所有异步操作都完成

核心场景 :处理并行的多个异步操作无论成功还是失败,都会等待所有操作完成 ,并返回每个操作的详细结果(包含状态和值/原因),解决了 Promise.all 快速失败的问题(适合需要知道所有操作结果的场景,如批量上传)。

核心特性
  • ES2020 引入,现代浏览器均支持;
  • 参数 iterable:可迭代对象,每个元素都是 Promise 实例;
  • 成功结果:当所有 Promise 都定型(settled),新 Promise 一定是 fulfilled 状态,返回结果数组,数组顺序与传入顺序一致;
  • 每个结果对象包含两个属性:
    • status:字符串,值为 fulfilledrejected
    • value:仅 statusfulfilled 时存在,是成功结果;
    • reason:仅 statusrejected 时存在,是失败原因。
使用示例(批量上传,需知道每个文件的上传结果)
javascript 复制代码
// 模拟3个文件上传的异步操作(2成功1失败)
const upload1 = Promise.resolve('文件1上传成功');
const upload2 = Promise.reject(new Error('文件2上传失败:文件过大'));
const upload3 = Promise.resolve('文件3上传成功');

// 等待所有上传操作完成,获取每个操作的结果
Promise.allSettled([upload1, upload2, upload3])
  .then(results => {
    console.log('所有上传结果:', results);
    // 筛选成功/失败的结果
    const successList = results.filter(item => item.status === 'fulfilled').map(item => item.value);
    const failList = results.filter(item => item.status === 'rejected').map(item => item.reason.message);
    console.log('成功的上传:', successList); // ['文件1上传成功', '文件3上传成功']
    console.log('失败的上传:', failList); // ['文件2上传失败:文件过大']
  });

6. Promise.any(iterable):第一个成功的异步操作决定结果

核心场景 :处理并行的多个异步操作忽略失败的操作,等待第一个成功的操作返回结果;若所有操作都失败,才返回聚合错误(适合多节点请求,取最快成功的节点数据)。

核心特性
  • ES2021 引入,现代浏览器均支持;
  • 参数 iterable:可迭代对象,每个元素都是 Promise 实例;
  • 成功机制:只要有一个 Promise 变为 fulfilled,新 Promise 立即继承其成功结果,其余未完成的 Promise 仍会执行,结果被忽略;
  • 失败机制:若所有 Promise 都变为 rejected,新 Promise 会变为 rejected,抛出 AggregateError 错误(包含所有失败原因)。
使用示例(多节点请求,取最快成功的结果)
javascript 复制代码
// 模拟3个节点的接口请求(2失败1成功,成功的节点最慢)
const requestNode1 = Promise.reject(new Error('节点1请求失败'));
const requestNode2 = Promise.reject(new Error('节点2请求失败'));
const requestNode3 = new Promise(resolve => setTimeout(() => resolve('节点3请求成功,获取数据'), 1000));

// 取第一个成功的请求结果
Promise.any([requestNode1, requestNode2, requestNode3])
  .then(res => console.log('最快成功的节点:', res)) // 输出:节点3请求成功,获取数据
  .catch(err => {
    console.log('所有节点都失败:', err);
    console.log('所有失败原因:', err.errors.map(e => e.message));
  });

// 所有操作都失败的情况
Promise.any([Promise.reject('失败1'), Promise.reject('失败2')])
  .catch(err => {
    console.log(err instanceof AggregateError); // true
    console.log(err.errors); // ['失败1', '失败2']
  });

⌛ 五、Promise 经典实战场景

1. 封装 AJAX 请求(Promise 版)

传统 AJAX 基于回调,封装为 Promise 后支持链式调用和统一错误处理,是前端最基础的实战场景:

javascript 复制代码
/**
 * Promise 版 AJAX 封装
 * @param {string} url - 请求地址
 * @param {string} method - 请求方法:GET/POST
 * @param {Object} data - 请求参数
 * @returns {Promise}
 */
function ajax({ url, method = 'GET', data = {} }) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    // 处理GET参数
    if (method.toUpperCase() === 'GET' && Object.keys(data).length > 0) {
      const params = new URLSearchParams(data).toString();
      url += '?' + params;
    }
    xhr.open(method, url, true);
    // 设置POST请求头
    if (method.toUpperCase() === 'POST') {
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    }
    // 响应处理
    xhr.onload = function() {
      if (xhr.status >= 200 && xhr.status < 300) {
        try {
          const res = JSON.parse(xhr.responseText); // 解析JSON
          resolve(res);
        } catch (err) {
          reject(new Error('响应数据解析失败:' + err.message));
        }
      } else {
        reject(new Error(`请求失败:状态码${xhr.status},${xhr.statusText}`));
      }
    };
    // 网络错误
    xhr.onerror = function() {
      reject(new Error('网络错误:无法连接到服务器'));
    };
    // 发送请求
    if (method.toUpperCase() === 'POST') {
      xhr.send(new URLSearchParams(data).toString());
    } else {
      xhr.send();
    }
  });
}

// 使用示例
ajax({
  url: '/api/user',
  method: 'GET',
  data: { id: 1 }
})
  .then(res => console.log('请求成功:', res))
  .catch(err => console.log('请求失败:', err.message));

2. 封装定时器(Promise 版)

将 setTimeout/setInterval 封装为 Promise,支持 async/await 调用,解决定时器嵌套问题:

javascript 复制代码
/**
 * Promise 版定时器(延迟执行)
 * @param {number} time - 延迟时间(毫秒)
 * @param {*} value - 延迟后返回的值
 * @returns {Promise}
 */
function delay(time, value) {
  return new Promise(resolve => {
    setTimeout(() => resolve(value), time);
  });
}

// 使用示例:async/await 调用
async function doTask() {
  console.log('开始执行任务');
  await delay(1000); // 延迟1秒
  console.log('1秒后执行下一步');
  const data = await delay(1000, '延迟2秒的返回值');
  console.log(data); // 延迟2秒的返回值
}
doTask();

3. 异步流程控制:串行/并行执行

3.1 串行执行(依次执行,后一个依赖前一个结果)

适合有顺序依赖的异步操作(如先获取token,再用token获取用户信息):

javascript 复制代码
// 模拟两个有依赖的异步操作
function getToken() {
  return new Promise(resolve => setTimeout(() => resolve('user_token_123'), 1000));
}
function getUserInfo(token) {
  return new Promise(resolve => setTimeout(() => resolve({ token, name: '张三', age: 18 }), 1000));
}

// 串行执行:async/await 更简洁(基于Promise)
async function getUserData() {
  const token = await getToken(); // 先获取token
  const userInfo = await getUserInfo(token); // 再用token获取用户信息
  console.log('用户数据:', userInfo);
}
getUserData(); // 总耗时2秒
3.2 并行执行(同时执行,无依赖)

适合无依赖的异步操作,提升执行效率(如同时请求多个无关联的接口):

javascript 复制代码
// 模拟3个无依赖的接口请求
function getArticleList() { return delay(1000, '文章列表'); }
function getCommentList() { return delay(1000, '评论列表'); }
function getLikeList() { return delay(1000, '点赞列表'); }

// 并行执行:Promise.all 实现,总耗时1秒(而非3秒)
async function getHomeData() {
  console.time('总耗时');
  // 同时执行,等待所有完成
  const [articles, comments, likes] = await Promise.all([
    getArticleList(),
    getCommentList(),
    getLikeList()
  ]);
  console.log('首页数据:', { articles, comments, likes });
  console.timeEnd('总耗时'); // 总耗时:约1000ms
}
getHomeData();

⚠️ 六、Promise 常见坑与避坑指南

1. 忘记写 return 导致链式调用断链

问题.then() 中未写 return,导致下一个 .then() 接收的参数是 undefined,且执行时机提前(因为返回的是默认的 Promise.resolve(undefined))。

javascript 复制代码
// 错误示例
Promise.resolve(1)
  .then(num => {
    console.log(num); // 1
    // 忘记return,默认返回undefined
  })
  .then(num => console.log(num)); // undefined(立即执行)

// 正确示例
Promise.resolve(1)
  .then(num => {
    console.log(num); // 1
    return num + 1; // 必须写return
  })
  .then(num => console.log(num)); // 2

2. 错误未被捕获(控制台报 UnhandledPromiseRejection)

问题 :Promise 被 reject 后,没有任何 .catch().then() 的 onRejected 处理,导致浏览器抛出未捕获错误。

javascript 复制代码
// 错误示例:无错误处理
Promise.reject(new Error('未捕获的错误')); // 控制台报错:UnhandledPromiseRejection

// 正确示例:必须添加catch
Promise.reject(new Error('已捕获的错误'))
  .catch(err => console.log(err.message)); // 正常输出

3. 认为 Promise 执行器是异步的

问题 :Promise 构造函数的执行器函数是同步立即执行的,内部的异步操作才是异步的,容易导致变量赋值顺序错误。

javascript 复制代码
// 错误认知:认为执行器是异步的
let a = 0;
new Promise((resolve) => {
  a = 1; // 同步执行,立即赋值
  resolve();
});
console.log(a); // 1(而非0)

// 正确理解:执行器同步,内部操作异步
let b = 0;
new Promise((resolve) => {
  setTimeout(() => {
    b = 1; // 异步执行,1秒后赋值
    resolve();
  }, 1000);
});
console.log(b); // 0(立即输出)

4. Promise.all 传入非 Promise 数组

问题Promise.all 传入的数组中包含普通值,会被自动转为 Promise.resolve(普通值),看似没问题,但如果包含同步错误,会立即触发失败。

javascript 复制代码
// 无害情况:普通值被转为成功的Promise
Promise.all([1, 2, Promise.resolve(3)])
  .then(res => console.log(res)); // [1,2,3]

// 危险情况:同步错误立即触发失败
Promise.all([1, 2, JSON.parse('{')]) // JSON.parse同步报错
  .then(res => console.log(res))
  .catch(err => console.log(err.message)); // Unexpected token } in JSON at position 1

避坑 :确保 Promise.all 数组中的元素要么是 Promise 实例,要么是安全的普通值(无同步错误)。

5. 多次调用 resolve/reject 无效

问题:认为多次调用 resolve/reject 可以改变 Promise 状态,实则状态一旦定型,后续调用均无效。

javascript 复制代码
new Promise((resolve, reject) => {
  resolve('第一次resolve');
  reject('reject'); // 无效
  resolve('第二次resolve'); // 无效
})
  .then(res => console.log(res)) // 第一次resolve
  .catch(err => console.log(err)); // 不会执行

⏫ 七、Promise 高级知识点:微任务与事件循环

Promise 的回调函数(.then()/.catch()/.finally())属于微任务(Microtask),这是 Promise 执行时机的关键,也是前端面试的高频考点。

1. 微任务与宏任务的区别

浏览器的事件循环中,任务分为宏任务(Macrotask)微任务(Microtask) ,执行顺序为:先执行同步代码 → 执行所有微任务 → 执行一个宏任务 → 再执行所有微任务 → 循环

  • 宏任务:setTimeout、setInterval、AJAX、DOM 事件、script 整体代码;
  • 微任务:Promise 回调、async/await、MutationObserver、queueMicrotask。

2. Promise 微任务执行示例

javascript 复制代码
console.log('1. 同步代码开始'); // 同步

setTimeout(() => {
  console.log('4. 宏任务:setTimeout'); // 宏任务,最后执行
}, 0);

new Promise((resolve) => {
  console.log('2. Promise执行器:同步'); // 同步
  resolve();
}).then(() => {
  console.log('3. 微任务:Promise.then'); // 微任务,同步后执行
});

console.log('5. 同步代码结束'); // 同步

输出顺序:1→2→5→3→4(核心:微任务在宏任务前执行)。

📕 八、Promise 与 async/await 结合使用

async/await 是 ES7 引入的Promise 语法糖 ,它让异步代码的写法完全同步化 ,是目前前端异步编程的最优方案,但它的底层依然是 Promise,必须掌握 Promise 才能真正理解 async/await。

1. 核心规则

  • async 修饰的函数,返回值一定是 Promise 实例(即使返回普通值,也会被转为 Promise.resolve(普通值));
  • await 只能在 async 函数中使用,用于等待 Promise 定型,暂停函数执行,直到 Promise 返回结果;
  • await 后面可以跟任意值,若不是 Promise,会被自动转为 Promise.resolve(值);
  • 错误处理:使用 try/catch 捕获 await 后的 Promise 错误(等价于 Promise 的 .catch())。

2. 结合使用示例(最简洁的异步代码)

javascript 复制代码
// 模拟异步操作
const fetchData = () => delay(1000, '获取到数据');
const fetchUser = () => delay(1000, { name: '张三' });

// async/await 写法,同步化流程
async function main() {
  try {
    console.log('开始执行');
    const data = await fetchData(); // 等待第一个异步操作
    console.log(data); // 获取到数据
    const user = await fetchUser(); // 等待第二个异步操作
    console.log(user); // { name: '张三' }
    console.log('所有操作完成');
  } catch (err) {
    console.log('错误:', err.message); // 捕获所有异步错误
  } finally {
    console.log('收尾操作'); // 无论成功/失败都执行
  }
}

main();

📌 九、总结

  1. Promise 是 ES6 核心的异步编程解决方案,解决了回调地狱问题,提供了统一的异步接口和集中的错误处理机制;
  2. Promise 有三种不可逆状态:pending、fulfilled、rejected,仅能通过 resolve/reject 完成两次状态转换,状态定型后不可修改;
  3. 实例方法 .then()/.catch()/.finally() 支持链式调用,核心原因是每个方法都返回新的 Promise 实例.catch() 统一捕获错误,.finally() 处理通用收尾;
  4. 构造函数静态方法是异步流程控制的核心:all(所有成功)、race(第一个完成)、allSettled(所有完成)、any(第一个成功)、resolve/reject(快速创建);
  5. Promise 回调属于微任务,执行顺序在同步代码后、宏任务前,这是理解 Promise 执行时机的关键;
  6. async/await 是 Promise 的语法糖,让异步代码同步化,是目前最优的异步写法,但底层依赖 Promise;
  7. 开发中避免常见坑:忘记 return 断链、未捕获错误、混淆执行器同步特性、多次调用 resolve/reject;
  8. Promise 是现代前端开发的必备技能,是 Vue、React 等框架异步操作、接口请求、状态管理的基础,也是前端面试的高频考点。

掌握 Promise 不仅能写出更优雅、更易维护的异步代码,更能理解现代前端异步编程的底层逻辑,为后续学习 async/await、事件循环、前端工程化打下坚实的基础。

相关推荐
jingling5552 小时前
uniapp | 基于高德地图实现位置选择功能(安卓端)
android·前端·javascript·uni-app
某公司摸鱼前端2 小时前
前端一键部署网站至服务器FTP
前端·javascript·uni-app
m0_647057962 小时前
uniapp使用rich-text流式 Markdown 换行问题与解决方案
前端·javascript·uni-app
We་ct2 小时前
LeetCode 49. 字母异位词分组:经典哈希解法解析+易错点规避
前端·算法·leetcode·typescript·哈希算法
CHU7290352 小时前
废品回收小程序前端功能设计逻辑与实践
前端·小程序
lzhdim2 小时前
微星首款全白设计的M-ATX小板! MPG B850M EDGE TIMAX WIF刀锋 钛评测:性能媲美顶级X870E主板
前端·edge
恋猫de小郭2 小时前
小米 HyperOS 4 大变样?核心应用以 Rust / Flutter 重写,不兼容老系统
android·前端·人工智能·flutter·ios
摘星编程2 小时前
OpenHarmony环境下React Native:Loading全屏加载遮罩
javascript·react native·react.js
李火火的安全圈2 小时前
基于Yakit、Wavely实现CVE-2025-55182(React Server Components(RSC)) 反序列化漏洞挖掘和POC编写
前端·react.js