JavaScript Promise 完全指南

1. 什么是 Promise(JavaScript%20Promise%20完全指南.md#1-什么是-promise) 2. Promise 的三种状态(JavaScript%20Promise%20完全指南.md#2-promise-的三种状态) 3. 创建 Promise(JavaScript%20Promise%20完全指南.md#3-创建-promise) 4. 实例方法(JavaScript%20Promise%20完全指南.md#4-实例方法) - .then()(JavaScript%20Promise%20完全指南.md#41-t

分享

官方文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
涵盖:每个方法、每个参数的含义、基础用法、进阶模式、最佳实践
最后更新:2026-03-05(经官方文档复核)


目录

  1. 什么是 Promise
  2. Promise 的三种状态
  3. 创建 Promise
  4. 实例方法
  5. 静态方法
  6. Promise 链式调用
  7. 错误处理模式
  8. async / await 与 Promise 的关系
  9. 执行顺序与微任务队列
  10. 常见陷阱
  11. 最佳实践
  12. 实战模式

1. 什么是 Promise

Promise 是 JavaScript 中表示异步操作最终结果的对象。它代表一个"承诺":现在不知道结果,但将来某个时刻会知道(成功或失败)。

引入 Promise 的原因:解决回调地狱(Callback Hell)。

// 回调地狱:嵌套难以维护
getUser(id, function(user) {
  getOrders(user.id, function(orders) {
    getOrderDetail(orders[0].id, function(detail) {
      // 越来越深...
    }, handleError);
  }, handleError);
}, handleError);

// Promise 链:线性、清晰
getUser(id)
  .then(user => getOrders(user.id))
  .then(orders => getOrderDetail(orders[0].id))
  .then(detail => console.log(detail))
  .catch(handleError);

2. Promise 的三种状态

状态 英文 含义 能否转换
等待中 pending 初始状态,操作尚未完成 可以 → fulfilled 或 rejected
已兑现 fulfilled 操作成功完成 不可逆
已拒绝 rejected 操作失败 不可逆
pending ──→ fulfilled
       └──→ rejected

关键规则

  • 状态一旦变为 fulfilledrejected,就永远不会再改变
  • 一个 Promise 只能被 resolve 或 reject 一次,后续调用无效
  • fulfilledrejected 统称为 settled(已落定)
const p = new Promise((resolve, reject) => {
  resolve('成功');   // 状态变为 fulfilled
  reject('失败');    // 这行被忽略,状态已确定
  resolve('再次');   // 同样被忽略
});

p.then(v => console.log(v)); // 输出: "成功"

3. 创建 Promise

3.1 构造函数 new Promise(executor)

const promise = new Promise(executor);

参数

executorFunction

执行器函数,在构造 Promise 时立即同步执行(不是异步的)。

executor(resolve, reject) => void
参数 类型 作用
resolve Function 调用后将 Promise 状态设为 fulfilled,传入的值作为成功结果
reject Function 调用后将 Promise 状态设为 rejected,传入的值作为失败原因
const p1 = new Promise((resolve, reject) => {
  // executor 是同步执行的
  console.log('executor 开始');      // 1. 先打印这里

  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      resolve('操作成功');             // 将 p1 变为 fulfilled
    } else {
      reject(new Error('操作失败'));   // 将 p1 变为 rejected
    }
  }, 1000);

  console.log('executor 结束');      // 2. 然后打印这里
});

console.log('Promise 已创建');       // 3. 最后打印这里(executor 同步执行)

resolve(value) 的特殊行为

resolve 传入的 value 会影响 Promise 的最终状态:

// 情况1:传入普通值 → 直接 fulfilled
resolve(42);
resolve('hello');
resolve({ data: [] });

// 情况2:传入另一个 Promise → 状态跟随那个 Promise
const inner = new Promise(resolve => setTimeout(() => resolve('inner'), 1000));
resolve(inner);  // p1 会等待 inner 完成,然后跟随其状态

// 情况3:传入 thenable 对象(有 .then 方法的对象)→ 跟随 thenable
resolve({
  then(resolve, reject) {
    resolve('thenable result');
  }
});

// 情况4:传入 undefined
resolve();       // fulfilled,值为 undefined

executor 中抛出异常

const p = new Promise((resolve, reject) => {
  throw new Error('executor 中的错误');
  // 等价于 reject(new Error('executor 中的错误'))
});

p.catch(err => console.log(err.message)); // "executor 中的错误"

// 注意:只有同步抛出才会被捕获!
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    throw new Error('异步抛出');  // 不会被 Promise 捕获,会变成未处理的异常!
  }, 100);
});

4. 实例方法

4.1 .then()

.then() 是 Promise 最核心的方法,用于注册成功和失败的回调。

promise.then(onFulfilled, onRejected)

参数

onFulfilledFunction | null | undefined

Promise 变为 fulfilled 时调用的函数。

onFulfilled(value) => any
内容 说明
参数 value Promise resolve 时传入的值
返回值 决定 .then() 返回的新 Promise 的状态(见下方详解)
若传入非函数 会被忽略,值透传到下一个 .then()

onRejectedFunction | null | undefined

Promise 变为 rejected 时调用的函数。

onRejected(reason) => any
内容 说明
参数 reason Promise reject 时传入的值(通常是 Error 对象)
返回值 同样决定新 Promise 的状态
若传入非函数 会被忽略,拒绝原因透传到下一个 .catch()

返回值规则(重要!)

.then() 总是返回一个新的 Promise,新 Promise 的状态取决于回调函数的返回值:

const p = Promise.resolve(1);

// 规则1:回调返回普通值 → 新 Promise fulfilled,值为该返回值
p.then(v => v + 1)                    // fulfilled(2)
 .then(v => console.log(v));          // 输出: 2

// 规则2:回调返回 Promise → 新 Promise 跟随该 Promise
p.then(v => Promise.resolve(v * 10)) // 返回的 Promise fulfilled(10)
 .then(v => console.log(v));         // 输出: 10

p.then(v => Promise.reject('error')) // 返回的 Promise rejected('error')
 .catch(e => console.log(e));        // 输出: "error"

// 规则3:回调中抛出异常 → 新 Promise rejected,reason 为该异常
p.then(v => { throw new Error('oops'); })
 .catch(e => console.log(e.message)); // 输出: "oops"

// 规则4:回调返回 undefined(没有 return 语句)→ fulfilled(undefined)
p.then(v => { console.log(v); })     // 没有返回值
 .then(v => console.log(v));         // 输出: undefined

// 规则5:onFulfilled 是非函数 → 值透传
p.then(null)                         // 透传
 .then(null)                         // 透传
 .then(v => console.log(v));         // 输出: 1

完整示例

fetch('/api/user')
  .then(
    // onFulfilled:请求成功
    (response) => {
      console.log('状态码:', response.status);
      return response.json(); // 返回新 Promise
    },
    // onRejected:网络错误(不推荐在这里处理,推荐用 .catch())
    (networkError) => {
      console.error('网络错误:', networkError);
      throw networkError; // 重新抛出,继续传递
    }
  )
  .then(data => {
    console.log('用户数据:', data);
  });

4.2 .catch()

.catch().then(null, onRejected) 的语法糖,专门用于捕获错误。

promise.catch(onRejected)
// 完全等价于
promise.then(undefined, onRejected)

参数

onRejectedFunction

onRejected(reason) => any
内容 说明
参数 reason 拒绝原因,通常是 Error 实例
返回值 决定 .catch() 返回的新 Promise 的状态

关键.catch() 返回的也是新 Promise,可以继续链式调用。

// 错误恢复:在 catch 中返回值,可以从错误中"恢复"
Promise.reject(new Error('数据库连接失败'))
  .catch(err => {
    console.log('错误:', err.message);
    return '使用缓存数据'; // 返回备用值,链式继续为 fulfilled 状态
  })
  .then(data => {
    console.log('数据:', data); // 输出: "使用缓存数据"
  });

// 错误传递:在 catch 中继续抛出,传递给下一个 catch
Promise.reject(new Error('原始错误'))
  .catch(err => {
    console.log('第一个 catch:', err.message);
    throw new Error('包装后的错误'); // 继续传递
  })
  .catch(err => {
    console.log('第二个 catch:', err.message); // 捕获包装后的错误
  });

为何推荐用 .catch() 而不是 .then() 的第二个参数

// 问题:.then(onFulfilled, onRejected) 中,onRejected 无法捕获 onFulfilled 中的错误
promise
  .then(
    data => { throw new Error('onFulfilled 中出错'); }, // 这个错误...
    err => console.log('这里捕获不到上面的错误')        // ...不会到这里
  );

// 推荐:.catch() 放在链末尾,可以捕获整条链中的所有错误
promise
  .then(data => { throw new Error('任何地方的错误'); })
  .then(data => { /* ... */ })
  .catch(err => console.log('统一捕获:', err.message)); // 能捕获上面任意一步的错误

4.3 .finally()

无论 Promise 成功还是失败,都会执行的回调。ES2018 引入。

promise.finally(onFinally)

参数

onFinallyFunction

onFinally() => any
内容 说明
参数 无参数(无法获知 Promise 的结果或原因)
返回值(普通值) 被忽略,原 Promise 的值或原因会透传
返回值(Promise) 等待该 Promise,若它 rejected 则覆盖原来的状态
// 透传行为演示
Promise.resolve('成功的值')
  .finally(() => {
    console.log('finally 执行了');
    return '这个返回值会被忽略'; // 被忽略
  })
  .then(v => console.log(v)); // 输出: "成功的值"(透传)

Promise.reject(new Error('失败原因'))
  .finally(() => {
    console.log('finally 也执行了');
    // 不 return 任何东西
  })
  .catch(e => console.log(e.message)); // 输出: "失败原因"(透传)

// finally 返回 rejected Promise 会覆盖原状态
Promise.resolve('原来的成功值')
  .finally(() => Promise.reject(new Error('finally 中的错误')))
  .catch(e => console.log(e.message)); // 输出: "finally 中的错误"

典型用途:资源清理

let connection;

getDbConnection()
  .then(conn => {
    connection = conn;
    return conn.query('SELECT * FROM users');
  })
  .then(users => {
    console.log(users);
  })
  .catch(err => {
    console.error('查询失败:', err);
  })
  .finally(() => {
    // 无论成功还是失败,都关闭连接
    if (connection) connection.close();
  });

5. 静态方法

5.1 Promise.resolve()

创建一个立即 fulfilled 的 Promise。

Promise.resolve(value)

参数

valueany

value 类型 返回结果
普通值(数字、字符串、对象等) 返回 fulfilled(value) 的 Promise
另一个 Promise 实例 直接返回该 Promise 本身(不包装)
Thenable 对象 返回跟随该 thenable 状态的 Promise
无参数 / undefined 返回 fulfilled(undefined)
// 普通值
Promise.resolve(42).then(v => console.log(v));         // 42
Promise.resolve('hello').then(v => console.log(v));    // "hello"

// 传入 Promise → 返回同一个对象
const original = new Promise(resolve => resolve(1));
const wrapped = Promise.resolve(original);
console.log(original === wrapped); // true!不会创建新 Promise

// 传入 thenable
Promise.resolve({
  then(resolve) { resolve('thenable value'); }
}).then(v => console.log(v)); // "thenable value"

// 常见用途:将同步值包装为 Promise,统一异步接口
function getData(id) {
  if (cache.has(id)) {
    return Promise.resolve(cache.get(id)); // 统一返回 Promise
  }
  return fetch(`/api/data/${id}`).then(r => r.json());
}

5.2 Promise.reject()

创建一个立即 rejected 的 Promise。

Promise.reject(reason)

参数

reasonany

拒绝原因,强烈建议传入 Error 实例(保留调用栈信息)。

// 推荐:传入 Error 实例
Promise.reject(new Error('出错了'))
  .catch(err => console.log(err.stack)); // 包含完整调用栈

// 不推荐:传入字符串(没有调用栈)
Promise.reject('出错了')
  .catch(reason => console.log(reason)); // 只有字符串

// 注意:与 Promise.resolve 不同,传入 Promise 不会跟随,而是直接作为 reason
const inner = Promise.resolve('inner');
Promise.reject(inner)
  .catch(reason => {
    console.log(reason === inner); // true,inner 被当作 reason,不会解包
  });

5.3 Promise.all()

等待所有 Promise 成功,任意一个失败则立即失败。

Promise.all(iterable)

参数

iterableIterable

包含 Promise(或任意值)的可迭代对象(通常是数组)。

  • 非 Promise 值会被 Promise.resolve() 包装
  • 空数组 [] 返回 fulfilled([])

返回值:新 Promise

情况 结果
所有 Promise 成功 fulfilled([result1, result2, ...]) — 结果数组顺序与输入一致
任意一个 Promise 失败 立即 rejected(reason) — 第一个失败的原因,其余 Promise 继续执行但结果被忽略
iterable 为空 fulfilled([])
// 基础用法:并发请求多个接口
const [user, orders, messages] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/orders').then(r => r.json()),
  fetch('/api/messages').then(r => r.json()),
]);

// 结果顺序与输入顺序一致,与完成时间无关
Promise.all([
  new Promise(r => setTimeout(() => r('慢'), 1000)),
  new Promise(r => setTimeout(() => r('快'), 100)),
]).then(([slow, fast]) => {
  console.log(slow); // "慢"(第一个输入,虽然后完成)
  console.log(fast); // "快"(第二个输入)
});

// 任意失败则整体失败
Promise.all([
  Promise.resolve(1),
  Promise.reject(new Error('第二个失败了')),
  Promise.resolve(3), // 这个成功了,但结果被忽略
]).catch(err => console.log(err.message)); // "第二个失败了"

// 混合普通值
Promise.all([1, Promise.resolve(2), 'three'])
  .then(results => console.log(results)); // [1, 2, "three"]

5.4 Promise.allSettled()

等待所有 Promise 落定(无论成功还是失败)。ES2020 引入。

Promise.allSettled(iterable)

参数:同 Promise.all()

返回值fulfilled(results)永远不会 reject

results 是对象数组,每个对象的格式:

// 成功时
{ status: 'fulfilled', value: any }

// 失败时
{ status: 'rejected', reason: any }
const results = await Promise.allSettled([
  Promise.resolve('成功1'),
  Promise.reject(new Error('失败')),
  Promise.resolve('成功2'),
]);

results.forEach(result => {
  if (result.status === 'fulfilled') {
    console.log('成功:', result.value);
  } else {
    console.log('失败:', result.reason.message);
  }
});
// 成功: 成功1
// 失败: 失败
// 成功: 成功2

// 与 Promise.all 的对比:
// Promise.all     → 任意失败就整体失败(快速失败)
// Promise.allSettled → 等全部完成再汇总(全量结果)

适用场景:批量操作中需要知道每个操作的结果。

// 批量上传文件,统计成功/失败数量
const uploadResults = await Promise.allSettled(
  files.map(file => uploadFile(file))
);

const succeeded = uploadResults.filter(r => r.status === 'fulfilled').length;
const failed = uploadResults.filter(r => r.status === 'rejected').length;
console.log(`上传完成:${succeeded} 成功,${failed} 失败`);

5.5 Promise.any()

返回第一个成功的 Promise,全部失败才失败。ES2021 引入。

Promise.any(iterable)

参数:同 Promise.all()

返回值:新 Promise

情况 结果
任意一个成功 立即 fulfilled(value) — 第一个成功的值
全部失败 rejected(AggregateError) — 包含所有失败原因
iterable 为空 rejected(AggregateError)
// 基础用法:多个数据源,取最快成功的那个
const data = await Promise.any([
  fetch('https://cdn1.example.com/data').then(r => r.json()),
  fetch('https://cdn2.example.com/data').then(r => r.json()),
  fetch('https://cdn3.example.com/data').then(r => r.json()),
]); // 哪个 CDN 最快返回就用哪个

// 全部失败时的错误
try {
  await Promise.any([
    Promise.reject(new Error('失败1')),
    Promise.reject(new Error('失败2')),
  ]);
} catch (err) {
  console.log(err instanceof AggregateError); // true
  console.log(err.message);                  // "All promises were rejected"
  console.log(err.errors);                   // [Error: 失败1, Error: 失败2]
}

5.6 Promise.race()

返回第一个落定(无论成功还是失败)的 Promise 的结果。

Promise.race(iterable)

参数:同 Promise.all()

返回值:新 Promise,状态与第一个落定的 Promise 相同。

情况 结果
第一个落定是 fulfilled fulfilled(value)
第一个落定是 rejected rejected(reason)
iterable 为空 永远 pending(!)
// 超时控制:最经典的用法
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error(`超时 ${ms}ms`)), ms)
  );
  return Promise.race([promise, timeout]);
}

try {
  const result = await withTimeout(fetch('/api/slow'), 3000);
} catch (err) {
  console.log(err.message); // 如果超时: "超时 3000ms"
}

// 注意:race 不会取消"落败"的 Promise,它们仍在后台执行
// 要真正取消,需要使用 AbortController

四个组合方法对比

方法 成功条件 失败条件 返回 引入版本
Promise.all 全部成功 任意失败(快速失败) 所有结果数组 ES2015
Promise.allSettled 全部落定 永不失败 所有状态对象数组 ES2020
Promise.any 任意成功 全部失败 第一个成功值 ES2021
Promise.race 第一个落定成功 第一个落定失败 第一个落定值 ES2015
Promise.try 回调正常返回 回调抛出异常 包装回调结果 ES2026

5.7 Promise.withResolvers()

返回一个包含 Promise 及其 resolve/reject 函数的对象,让你在 Promise 外部控制其状态。ES2024 引入。

Promise.withResolvers()

返回值{ promise, resolve, reject }

// 传统写法:需要在 executor 内部保存引用
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

// withResolvers 写法:更简洁
const { promise, resolve, reject } = Promise.withResolvers();

// 在任意地方控制 Promise
document.getElementById('btn').addEventListener('click', () => {
  resolve('用户点击了按钮');
});

const result = await promise;
console.log(result);

5.8 Promise.try()

将任意类型的回调(同步或异步、返回值或抛出异常)统一包装成 Promise。ES2026 引入(Baseline 2025)。

Promise.try(func)
Promise.try(func, arg1, arg2, ...argN)

参数

参数 类型 说明
func Function 同步调用的回调函数,可以返回值、抛出异常或返回 Promise
arg1...argN any 传给 func 的参数列表

返回值:新 Promise

  • func 同步返回值 → 已 fulfilled 的 Promise
  • func 同步抛出异常 → 已 rejected 的 Promise
  • func 返回 Promise → 跟随该 Promise
// 统一处理同步和异步回调
function doSomething(action) {
  return Promise.try(action)
    .then(result => console.log(result))
    .catch(err => console.error(err))
    .finally(() => console.log('完成'));
}

doSomething(() => '同步结果');                  // 同步返回值
doSomething(() => { throw new Error('同步错误'); }); // 同步抛出
doSomething(async () => '异步结果');            // 返回 Promise

// 对比旧写法:
// Promise.resolve(func()) 不能捕获同步抛出
// new Promise(resolve => resolve(func())) 才等价,但更繁琐

典型用途:消除回调是同步还是异步的不确定性,统一用 Promise 链处理。


6. Promise 链式调用

6.1 链式调用的工作原理

每个 .then() / .catch() / .finally() 都返回一个新的 Promise,因此可以无限链接。

Promise.resolve(1)
  .then(v => v + 1)          // 返回 fulfilled(2)
  .then(v => v * 3)          // 返回 fulfilled(6)
  .then(v => {
    if (v > 5) throw new Error('太大了');
  })                          // 返回 rejected(Error)
  .catch(err => {
    console.log(err.message); // "太大了"
    return 0;                 // 从错误中恢复,返回 fulfilled(0)
  })
  .then(v => console.log(v)); // 0

6.2 常见错误:不返回内层 Promise

// 错误:没有 return,导致链断裂
fetch('/api/user')
  .then(response => {
    response.json().then(data => {  // 这里没有 return!
      console.log(data);
    });
    // 这个 .then 回调没有明确返回,
    // 所以下一个 .then 不会等待 response.json() 完成
  })
  .then(() => {
    // 这里可能在 data 被打印之前就执行了
  });

// 正确:return 内层 Promise
fetch('/api/user')
  .then(response => {
    return response.json(); // 必须 return!
  })
  .then(data => {
    console.log(data); // 保证在数据解析后执行
  });

7. 错误处理模式

7.1 链末捕获(推荐)

doStep1()
  .then(doStep2)
  .then(doStep3)
  .catch(err => {
    // 捕获以上任意一步的错误
    console.error('某一步出错了:', err);
  });

7.2 局部捕获(错误不扩散)

doStep1()
  .then(result1 =>
    doStep2(result1).catch(err => {
      // 只捕获 step2 的错误,提供默认值
      console.warn('step2 失败,使用默认值');
      return defaultValue; // 继续链式
    })
  )
  .then(doStep3)
  .catch(err => {
    // 只有 step1 和 step3 的错误到达这里
  });

7.3 错误转换

function getUser(id) {
  return fetch(`/api/users/${id}`)
    .then(response => {
      if (!response.ok) {
        // 将 HTTP 错误转换为有意义的自定义错误
        throw new UserNotFoundError(`用户 ${id} 不存在,状态码: ${response.status}`);
      }
      return response.json();
    });
}

7.4 未处理的 Promise 拒绝

// 在 Node.js 中监听未处理的 rejection
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的 Promise 拒绝:', reason);
});

// 在浏览器中
window.addEventListener('unhandledrejection', event => {
  console.error('未处理的 Promise 拒绝:', event.reason);
  event.preventDefault(); // 阻止默认的控制台错误
});

8. async / await 与 Promise 的关系

async/await 是 Promise 的语法糖,底层仍然是 Promise。

8.1 对应关系

// Promise 写法
function fetchUser(id) {
  return fetch(`/api/users/${id}`)
    .then(res => res.json())
    .then(user => {
      console.log(user.name);
      return user;
    })
    .catch(err => {
      console.error(err);
      throw err;
    });
}

// async/await 写法(等价)
async function fetchUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    const user = await res.json();
    console.log(user.name);
    return user; // async 函数自动包装为 Promise.resolve(user)
  } catch (err) {
    console.error(err);
    throw err; // async 函数中的 throw 等价于 Promise.reject(err)
  }
}

8.2 async 函数的返回值

async function f1() { return 42; }
// 等价于
function f1() { return Promise.resolve(42); }

async function f2() { throw new Error('err'); }
// 等价于
function f2() { return Promise.reject(new Error('err')); }

async function f3() { return Promise.resolve(42); }
// 返回 fulfilled(42),不会双重包装

8.3 并发控制

// 错误:串行执行(每个 await 等待上一个完成)
async function serial() {
  const a = await fetch('/api/a');  // 先等这个
  const b = await fetch('/api/b');  // 再等这个(总时间 = a + b)
  return [a, b];
}

// 正确:并发执行
async function concurrent() {
  const [a, b] = await Promise.all([
    fetch('/api/a'),
    fetch('/api/b'),
  ]); // 总时间 = max(a, b)
  return [a, b];
}

// 先触发,后等待
async function concurrent2() {
  const promiseA = fetch('/api/a'); // 立即触发
  const promiseB = fetch('/api/b'); // 立即触发(不加 await)
  const a = await promiseA;         // 然后等待
  const b = await promiseB;
  return [a, b];
}

9. 执行顺序与微任务队列

Promise 的回调作为微任务(Microtask)执行,优先于宏任务(Macrotask)

console.log('1. 同步代码 开始');

setTimeout(() => console.log('5. 宏任务 (setTimeout)'), 0);

Promise.resolve()
  .then(() => console.log('3. 微任务 1'))
  .then(() => console.log('4. 微任务 2'));

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

// 输出顺序:
// 1. 同步代码 开始
// 2. 同步代码 结束
// 3. 微任务 1
// 4. 微任务 2
// 5. 宏任务 (setTimeout)

执行模型

同步代码(调用栈)
    ↓ 清空
微任务队列(Promise.then/catch/finally)
    ↓ 全部清空
宏任务队列(setTimeout, setInterval, I/O...)
    ↓ 取一个执行
微任务队列(处理本轮宏任务产生的微任务)
    ↓ 全部清空
...
// 嵌套的微任务
Promise.resolve().then(() => {
  console.log('微任务 A');
  Promise.resolve().then(() => {
    console.log('微任务 A 的子任务'); // 在 B 之前!
  });
}).then(() => {
  console.log('微任务 B');
});

// 输出:
// 微任务 A
// 微任务 A 的子任务
// 微任务 B

10. 常见陷阱

陷阱1:在循环中使用 Promise

const ids = [1, 2, 3, 4, 5];

// 错误:forEach 不等待 async 回调
ids.forEach(async (id) => {
  const data = await fetchData(id); // forEach 不 await 这个
  process(data);
});
// 以上代码立即结束,所有请求"发出"但不等待

// 正确:使用 Promise.all(并发)
await Promise.all(ids.map(id => fetchData(id).then(process)));

// 正确:使用 for...of(串行,保证顺序)
for (const id of ids) {
  const data = await fetchData(id);
  process(data);
}

陷阱2:Promise 构造函数中的异步 throw

// 错误:异步抛出不会被 Promise 捕获
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    throw new Error('异步错误'); // 未捕获异常!会崩溃
  }, 100);
});

// 正确:必须调用 reject
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    try {
      // ...
    } catch (err) {
      reject(err); // 必须手动 reject
    }
  }, 100);
});

陷阱3:忘记 return

// 陷阱:两个独立的 Promise 链,不是一个链
function processData() {
  fetch('/api/data')
    .then(r => r.json())
    .then(data => save(data)); // 这里忘记 return
  // 函数返回 undefined,调用方无法知道何时完成
}

// 正确
function processData() {
  return fetch('/api/data')   // return!
    .then(r => r.json())
    .then(data => save(data));
}

性能陷阱4:不必要的 new Promise 包装

// 反模式:Promise 构造函数反模式(Explicit Promise Construction Anti-Pattern)
function getUser(id) {
  return new Promise((resolve, reject) => {
    fetch(`/api/users/${id}`)  // fetch 本身已经返回 Promise!
      .then(r => r.json())
      .then(resolve)           // 多此一举
      .catch(reject);          // 多此一举
  });
}

// 正确:直接返回 Promise 链
function getUser(id) {
  return fetch(`/api/users/${id}`).then(r => r.json());
}

陷阱5:吞掉错误

// 危险:空的 catch 把错误吞掉
promise.catch(() => {}); // 错误消失,无法调试

// 至少记录一下
promise.catch(err => console.error('Promise 错误:', err));

11. 最佳实践

1. 始终处理拒绝

// 每个 Promise 链都应该有错误处理
somePromise()
  .then(handleSuccess)
  .catch(handleError); // 不能省

2. 避免嵌套,保持链式

// 避免嵌套 Promise
// 坏
p1.then(r1 => {
  p2(r1).then(r2 => {
    p3(r2).then(r3 => {
      // 嵌套地狱回归了
    });
  });
});

// 好:平铺链式
p1.then(r1 => p2(r1))
  .then(r2 => p3(r2))
  .then(r3 => { /* 处理 r3 */ })
  .catch(handleError);

3. reject 时传入 Error 对象

// 坏
reject('something went wrong');
reject({ code: 500, message: 'error' });

// 好:保留调用栈,便于调试
reject(new Error('something went wrong'));
reject(Object.assign(new Error('error'), { code: 500 }));

4. 用 Promise.all 做并发,而非串行

// 独立的异步操作应该并发
const [user, config] = await Promise.all([getUser(), getConfig()]);

5. 使用 AbortController 取消请求

const controller = new AbortController();

const promise = fetch('/api/data', {
  signal: controller.signal,
});

// 取消
setTimeout(() => controller.abort(), 3000);

try {
  const data = await promise;
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('请求被取消');
  }
}

6. 注意 finally 的透传特性

// finally 中的普通返回值被忽略,但不要依赖这一"隐式"行为
// 如果在 finally 中需要修改结果,使用 then

// 如果 finally 中有异步操作,必须 return Promise
.finally(async () => {
  await cleanup(); // 必须 return 才等待!
})
// 应改为:
.finally(() => cleanup()) // 返回 cleanup() 的 Promise

12. 实战模式

12.1 重试机制

/**
 * 带重试的 Promise 执行器
 * @param {Function} fn - 返回 Promise 的函数
 * @param {number} retries - 最大重试次数
 * @param {number} delay - 重试间隔(ms)
 */
function retry(fn, retries = 3, delay = 1000) {
  return fn().catch(err => {
    if (retries <= 0) throw err;
    console.log(`失败,${delay}ms 后重试,剩余次数: ${retries}`);
    return new Promise(resolve => setTimeout(resolve, delay))
      .then(() => retry(fn, retries - 1, delay * 2)); // 指数退避
  });
}

// 使用
const data = await retry(() => fetch('/api/unstable').then(r => r.json()));

12.2 并发限制

/**
 * 限制并发数量的 Promise 执行器
 * @param {Array} tasks - 任务数组(每个元素是返回 Promise 的函数)
 * @param {number} concurrency - 最大并发数
 */
async function limitConcurrency(tasks, concurrency) {
  const results = [];
  const executing = new Set();

  for (const task of tasks) {
    const promise = task().then(result => {
      executing.delete(promise);
      return result;
    });
    executing.add(promise);
    results.push(promise);

    if (executing.size >= concurrency) {
      await Promise.race(executing); // 等待最快完成的那个
    }
  }

  return Promise.all(results);
}

// 使用:同时最多 3 个请求
const results = await limitConcurrency(
  urls.map(url => () => fetch(url).then(r => r.json())),
  3
);

12.3 超时 + 取消

function fetchWithTimeout(url, ms = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), ms);

  return fetch(url, { signal: controller.signal })
    .finally(() => clearTimeout(timeoutId)); // 成功后清除定时器
}

12.4 缓存 Promise(防止重复请求)

const cache = new Map();

function cachedFetch(url) {
  if (cache.has(url)) {
    return cache.get(url); // 返回同一个 Promise(即使还没完成)
  }

  const promise = fetch(url)
    .then(r => r.json())
    .catch(err => {
      cache.delete(url); // 失败时清除缓存,允许重试
      throw err;
    });

  cache.set(url, promise);
  return promise;
}

// 即使短时间内被调用多次,也只发出一个 HTTP 请求
cachedFetch('/api/config');
cachedFetch('/api/config'); // 返回同一个 Promise

12.5 事件转 Promise

// 将事件监听器转换为 Promise
function waitForEvent(emitter, event, errorEvent = 'error') {
  return new Promise((resolve, reject) => {
    const onSuccess = (data) => {
      emitter.removeListener(errorEvent, onError);
      resolve(data);
    };
    const onError = (err) => {
      emitter.removeListener(event, onSuccess);
      reject(err);
    };
    emitter.once(event, onSuccess);
    emitter.once(errorEvent, onError);
  });
}

// 使用
const data = await waitForEvent(stream, 'data');
await waitForEvent(connection, 'open');

12.6 顺序执行任务队列

// 将一组异步任务按顺序执行,并收集结果
function sequentialPromises(tasks) {
  return tasks.reduce(
    (chain, task) => chain.then(results =>
      task().then(result => [...results, result])
    ),
    Promise.resolve([])
  );
}

const results = await sequentialPromises([
  () => fetch('/api/step1').then(r => r.json()),
  () => fetch('/api/step2').then(r => r.json()),
  () => fetch('/api/step3').then(r => r.json()),
]);

快速参考卡

创建
  new Promise((resolve, reject) => {...})   手动创建
  Promise.resolve(value)                    立即成功
  Promise.reject(reason)                    立即失败
  Promise.withResolvers()                   外部控制

实例方法
  .then(onFulfilled, onRejected?)           成功/失败回调
  .catch(onRejected)                        等价 .then(null, fn)
  .finally(onFinally)                       无论如何都执行

组合方法
  Promise.all([...])                        全成功 / 一失败立即失败
  Promise.allSettled([...])                 全落定 / 永不失败
  Promise.any([...])                        一成功 / 全失败才失败
  Promise.race([...])                       第一个落定(成功或失败)
  Promise.try(fn, ...args)                  包装任意回调为 Promise(ES2026)

最佳实践

优先用 async/await 而非 .thenasync/await 使控制流更线性,错误处理用 try/catch.catch 更直观,尤其是多个顺序异步操作时。

并发请求用 Promise.allSettled 代替 Promise.allPromise.all 中任一失败即短路,适合"全部成功才有意义"的场景;Promise.allSettled 等所有请求完成后检查每个结果,适合批量操作需要汇总失败列表的场景。

Promise 链末尾始终加 .catch:未捕获的 Promise 拒绝在 Node.js 中会导致进程崩溃(--unhandled-rejections=throw),在浏览器中触发 unhandledrejection 事件。

Promise.race 实现超时控制

const timeout = new Promise((_, reject) =>
  setTimeout(() => reject(new Error('Timeout')), 5000)
);
const result = await Promise.race([fetch(url), timeout]);

避免在循环中顺序 awaitfor 循环中 await 会逐个等待,所有请求串行执行;改用 Promise.all 并发:

// 差:串行,总耗时 = sum(各请求时间)
for (const url of urls) await fetch(url);

// 好:并发,总耗时 ≈ max(各请求时间)
await Promise.all(urls.map(url => fetch(url)));

常见陷阱

陷阱:forEachawait 不等待内部 Promise

现象: arr.forEach(async item => { await process(item) }) 执行后数据没有处理完,函数已经返回。
原因: forEach 不处理回调的返回值(Promise),内部 await 只在回调协程内暂停,外层 forEach 继续执行。
解决: 改用 for...ofPromise.all(arr.map(async item => { await process(item) }))

陷阱:async 函数总返回 Promise,普通 return 值也被包裹

现象: const val = myAsyncFn() 以为得到返回值,实际是 Promise 对象。
原因: async 函数的返回值自动包在 Promise.resolve() 中,调用方必须 await
解决: 调用 async 函数时始终 await,或用 .then() 处理结果。

陷阱:Promise 构造函数中同步抛出的错误不被捕获

现象: new Promise(() => { throw new Error('sync') }) 中抛出的错误没有被外部 .catch 捕获。
原因: Promise 构造函数的 executor 中同步抛出会被转换为拒绝,.catch 可以捕获;但若在 setTimeout 等异步回调中抛出,则脱离 Promise 链,变为未捕获异常。
解决: 在 executor 的异步回调中用 reject(error) 而非 throw


参见

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog