Promise 完全指南(核心概念+实战用法+避坑)

Promise 是 ES6 引入的异步编程解决方案,用于解决传统回调函数嵌套导致的「回调地狱」问题,统一异步操作的处理范式,让异步代码更易读、易维护。它本质是一个「状态机」,描述异步操作的最终状态(成功/失败),并提供统一的结果/错误处理方式。

一、核心概念

1. Promise 的核心定位

解决异步编程的痛点:

  • 回调地狱:多层异步回调嵌套(如 ajax1(() => { ajax2(() => { ajax3() }) })),代码臃肿、难以调试;
  • 异步操作标准化:统一异步操作的「成功/失败」处理逻辑,替代混乱的回调写法。

2. Promise 的三种状态(核心!状态不可逆)

状态含义能否切换
pending进行中(初始状态)可切换为 fulfilled 或 rejected
fulfilled已成功(已解决)一旦切换,状态永久固定,不可再变
rejected已失败(已拒绝)一旦切换,状态永久固定,不可再变

关键:Promise 状态一旦从 pending 变为 fulfilled/rejected,就无法再修改(比如调用 resolve 后再调用 reject 无效)。

3. 核心回调函数

创建 Promise 时需传入一个「执行器函数」,接收两个内置回调:

  • resolve:异步操作成功时调用,将状态改为 fulfilled,并传递成功结果;
  • reject:异步操作失败时调用,将状态改为 rejected,并传递错误信息。

二、Promise 基本用法

1. 基础创建与执行

// 1. 创建 Promise 实例
const promise = new Promise((resolve, reject) => {
  // 模拟异步操作(如接口请求、定时器、文件读取)
  setTimeout(() => {
    const randomNum = Math.random();
    if (randomNum > 0.5) {
      // 成功:调用 resolve,传递结果
      resolve({ code: 200, data: `随机数:${randomNum}` });
    } else {
      // 失败:调用 reject,传递错误
      reject({ code: 500, msg: `随机数过小:${randomNum}` });
    }
  }, 1000);
});

// 2. 处理 Promise 结果(then 处理成功,catch 处理失败)
promise
  .then((res) => {
    console.log('成功:', res); // 状态为 fulfilled 时执行
  })
  .catch((err) => {
    console.log('失败:', err); // 状态为 rejected 时执行
  })
  .finally(() => {
    console.log('无论成功/失败,都会执行'); // 可选:最终回调(ES2018 新增)
  });

2. 关键特性:异步执行

Promise 执行器函数立即执行,但 then/catch/finally 中的回调会放入「微任务队列」,等待同步代码执行完成后再执行:

console.log('同步代码开始');
const p = new Promise((resolve) => {
  console.log('执行器函数(同步)');
  resolve('异步结果');
});
p.then((res) => {
  console.log('then 回调(微任务):', res);
});
console.log('同步代码结束');

// 输出顺序:
// 同步代码开始 → 执行器函数(同步) → 同步代码结束 → then 回调(微任务):异步结果

三、Promise 常用 API

1. 实例方法(Promise 实例调用)

方法作用
then()处理成功状态:接收两个参数(可选),第一个处理 fulfilled,第二个处理 rejected
catch()处理失败状态:捕获 rejected 状态或 then 中抛出的错误
finally()最终回调:无论成功/失败都会执行,无参数(无法获取结果/错误)
示例:then 的第二个参数(不推荐,优先用 catch)
promise.then(
  (res) => console.log('成功', res), // 成功回调
  (err) => console.log('失败', err)  // 失败回调(等价于 catch,但无法捕获前一个 then 的错误)
);

2. 静态方法(Promise 构造函数调用)

(1)Promise.resolve():快速创建「已成功」的 Promise
// 场景:将非 Promise 值转为 Promise(统一异步接口)
const p1 = Promise.resolve(123); // 等价于 new Promise(resolve => resolve(123))
p1.then(res => console.log(res)); // 123

// 若参数是 Promise,直接返回该 Promise
const p2 = Promise.resolve(p1);
console.log(p1 === p2); // true
(2)Promise.reject():快速创建「已失败」的 Promise
const p = Promise.reject('出错了');
p.catch(err => console.log(err)); // 出错了
(3)Promise.all():并行执行多个 Promise,「全部成功」才返回
  • 入参:Promise 数组(或可迭代对象);
  • 结果:返回新 Promise,成功结果为数组(顺序与入参一致);
  • 失败:只要有一个 Promise 失败,立即返回该失败结果(其余 Promise 仍会执行,但结果被忽略)。
// 示例:并行请求多个接口
const p1 = Promise.resolve('接口1结果');
const p2 = Promise.resolve('接口2结果');
const p3 = Promise.resolve('接口3结果');

Promise.all([p1, p2, p3])
  .then(res => console.log('全部成功:', res)) // ['接口1结果', '接口2结果', '接口3结果']
  .catch(err => console.log('有失败:', err));

// 若 p2 失败:
const p2Error = Promise.reject('接口2失败');
Promise.all([p1, p2Error, p3])
  .catch(err => console.log(err)); // 接口2失败(p1、p3 仍执行,但结果被忽略)
(4)Promise.race():并行执行多个 Promise,「第一个完成」就返回
  • 入参:Promise 数组;
  • 结果:返回新 Promise,结果为「第一个完成(成功/失败)」的 Promise 结果。
// 场景:请求超时控制(5秒未响应则触发超时)
const request = new Promise((resolve) => setTimeout(() => resolve('请求成功'), 6000));
const timeout = new Promise((_, reject) => setTimeout(() => reject('请求超时'), 5000));

Promise.race([request, timeout])
  .then(res => console.log(res))
  .catch(err => console.log(err)); // 请求超时(因为 timeout 先完成)
(5)Promise.allSettled():并行执行多个 Promise,「全部完成」后返回(无论成败)
  • ES2020 新增,解决 all 「一错全错」的问题;
  • 结果:返回新 Promise,成功结果为数组,每个元素包含 status(fulfilled/rejected)和 value/reason。
const p1 = Promise.resolve('成功1');
const p2 = Promise.reject('失败2');

Promise.allSettled([p1, p2])
  .then(res => {
    console.log(res);
    // 输出:
    // [
    //   { status: 'fulfilled', value: '成功1' },
    //   { status: 'rejected', reason: '失败2' }
    // ]
  });
(6)Promise.any():并行执行多个 Promise,「第一个成功」就返回
  • ES2021 新增,与 race 相反:只关注第一个成功的 Promise;
  • 失败:所有 Promise 都失败时,返回 AggregateError(包含所有错误)。
const p1 = Promise.reject('失败1');
const p2 = Promise.resolve('成功2');
const p3 = Promise.reject('失败3');

Promise.any([p1, p2, p3])
  .then(res => console.log(res)) // 成功2
  .catch(err => console.log(err)); // 仅所有失败时执行

四、链式调用(解决回调地狱)

1. 回调地狱 vs Promise 链式

// 传统回调地狱(多层嵌套)
ajax('url1', (res1) => {
  ajax('url2?id=' + res1.id, (res2) => {
    ajax('url3?name=' + res2.name, (res3) => {
      console.log('最终结果:', res3);
    }, (err3) => console.log('err3', err3));
  }, (err2) => console.log('err2', err2));
}, (err1) => console.log('err1', err1));

// Promise 链式调用(扁平化)
function request(url) {
  return new Promise((resolve, reject) => {
    ajax(url, resolve, reject);
  });
}

request('url1')
  .then(res1 => request('url2?id=' + res1.id)) // 返回新 Promise,链式继续
  .then(res2 => request('url3?name=' + res2.name))
  .then(res3 => console.log('最终结果:', res3))
  .catch(err => console.log('任意一步出错:', err)); // 统一捕获所有错误

2. 链式调用关键:then 必须返回 Promise

then 中返回非 Promise 值时,会被 Promise.resolve() 包装为成功的 Promise,确保链式可延续:

Promise.resolve(1)
  .then(res => res + 1) // 返回 2 → 包装为 Promise.resolve(2)
  .then(res => res * 2) // 返回 4 → 包装为 Promise.resolve(4)
  .then(res => console.log(res)); // 4

五、错误处理

1. catch 捕获所有上游错误

catch 能捕获:

  • Promise 执行器中调用 reject();
  • then/finally 中抛出的同步错误;
  • 上游 Promise 链中的任何错误。
new Promise((resolve, reject) => {
  reject('执行器失败'); // 触发 catch
})
  .then(res => {
    console.log(res);
    throw new Error('then 中抛出错误'); // 若执行到这里,也会触发 catch
  })
  .catch(err => console.log('捕获错误:', err)); // 捕获「执行器失败」或「then 抛出的错误」

2. async/await 结合 Promise(实战主流写法)

async/await 是 ES2017 语法糖,基于 Promise 实现,让异步代码「同步化」:

// 封装异步请求为 Promise
function fetchData(url) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`数据:${url}`), 1000);
  });
}

// async/await 调用(需包裹在 async 函数中)
async function getData() {
  try {
    const res1 = await fetchData('url1'); // 等待 Promise 成功
    const res2 = await fetchData(`url2?${res1}`);
    console.log('最终数据:', res2);
  } catch (err) {
    console.log('错误:', err); // 捕获所有 await 中的 Promise 失败
  }
}

getData();

六、实战场景示例

1. 封装接口请求(Axios 本质是 Promise)

// 模拟 Axios 封装
function request(options) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(options.method || 'GET', options.url);
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText));
      } else {
        reject({ status: xhr.status, msg: '请求失败' });
      }
    };
    xhr.onerror = () => reject({ msg: '网络错误' });
    xhr.send(options.data ? JSON.stringify(options.data) : null);
  });
}

// 使用
request({ url: '/api/user', method: 'POST', data: { id: 1 } })
  .then(res => console.log(res))
  .catch(err => console.log(err));

2. 并行请求 + 数据整合

// 并行请求用户和订单数据,整合后返回
async function getUserAndOrder(userId) {
  try {
    // 并行执行两个 Promise(比串行快)
    const [user, order] = await Promise.all([
      request({ url: `/api/user/${userId}` }),
      request({ url: `/api/order/${userId}` })
    ]);
    return { ...user, orderList: order.list }; // 整合数据
  } catch (err) {
    console.log('请求失败:', err);
    return null;
  }
}

getUserAndOrder(1).then(res => console.log(res));

七、常见坑点与避坑

1. 忘记 return Promise 导致链式断裂

// 错误写法:then 中未返回 Promise,后续 then 拿到 undefined
Promise.resolve(1)
  .then(res => {
    res + 1; // 无 return,等价于 return undefined
  })
  .then(res => console.log(res)); // undefined

// 正确写法:return 结果(或 Promise)
Promise.resolve(1)
  .then(res => res + 1) // return 2
  .then(res => console.log(res)); // 2

2. 状态不可逆导致的逻辑错误

new Promise((resolve, reject) => {
  resolve('成功'); // 状态变为 fulfilled
  reject('失败'); // 无效!状态已固定,不会触发 catch
})
  .then(res => console.log(res)) // 成功
  .catch(err => console.log(err)); // 不执行

3. Promise.all 「一错全错」的风险

若需兼容部分请求失败的场景,改用 Promise.allSettled():

// 需求:即使部分接口失败,也要获取成功的结果
async function getMultiData(urls) {
  const results = await Promise.allSettled(urls.map(url => request({ url })));
  // 过滤出成功的结果
  const successData = results
    .filter(item => item.status === 'fulfilled')
    .map(item => item.value);
  return successData;
}

4. 未捕获的 Promise 错误

Promise 错误若未用 catch 捕获,会触发控制台报错(Node.js 甚至会终止进程):

// 错误:无 catch 捕获
new Promise((_, reject) => reject('出错了'));

// 正确:始终添加 catch,或用 unhandledrejection 全局监听
window.addEventListener('unhandledrejection', (e) => {
  e.preventDefault(); // 阻止控制台报错
  console.log('全局捕获 Promise 错误:', e.reason);
});

八、总结

1. 核心价值

  • 解决回调地狱,让异步代码扁平化;
  • 统一异步操作的成功/失败处理逻辑;
  • 提供丰富的静态方法(all/race 等),满足多异步场景需求。

2. 实战选型

场景推荐 API
单个异步操作基础 Promise + then/catch
多个异步并行,需全部成功Promise.all
多个异步并行,需全部完成(无论成败)Promise.allSettled
多个异步并行,取第一个成功Promise.any
异步操作超时控制Promise.race
简化异步代码书写async/await(基于 Promise)

3. 关键原则

  • 始终为 Promise 添加 catch 错误处理;
  • 链式调用时确保 then 返回值(尤其是 Promise);
  • 多异步场景根据「是否允许部分失败」选择 all/allSettled/any。

Promise 是现代 JS 异步编程的基础,axios、fetch(封装后)、Node.js 异步 API 等均基于 Promise 实现,掌握它是理解 async/await、微任务等高级概念的核心前提。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐