javascript

Promise 与 async/await

By AI-Writer 18 min read

JavaScript 是单线程语言,异步编程是其核心能力之一。从回调函数到 Promise,再到 async/await,异步代码的写法变得越来越接近同步代码的直观性。

回调地狱与 Promise 的诞生

在 Promise 出现之前,多层嵌套的回调函数让代码难以维护:

javascript
// 回调地狱(Callback Hell)
readFile('a.txt', (err, data) => {
  if (err) { /* 处理错误 */ return; }
  parse(data, (err, result) => {
    if (err) { /* 处理错误 */ return; }
    writeFile('b.txt', result, (err) => {
      if (err) { /* 处理错误 */ return; }
      // ...
    });
  });
});

Promise 提供了一种更优雅的方式来组织异步代码。

Promise 基础

三种状态

一个 Promise 始终处于以下三种状态之一:

  • pending(待定):初始状态
  • fulfilled(已完成):操作成功
  • rejected(已拒绝):操作失败

状态一旦改变就不可再次更改:pending → fulfilledpending → rejected

创建 Promise

javascript
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      resolve('操作成功!');   // 状态变为 fulfilled
    } else {
      reject(new Error('操作失败')); // 状态变为 rejected
    }
  }, 1000);
});

消费 Promise

javascript
promise
  .then(result => {
    console.log(result); // "操作成功!"
    return result.toUpperCase();
  })
  .then(upper => {
    console.log(upper); // "操作成功!"
  })
  .catch(error => {
    console.error(error.message); // 捕获前面任何阶段的错误
  })
  .finally(() => {
    console.log('无论成败都会执行');
  });

then 可以接收两个参数:then(onFulfilled, onRejected),但通常将错误处理统一交给 catch

then 的链式传递

javascript
function fetchUser(id) {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ id, name: 'Alice' }), 100);
  });
}

function fetchOrders(user) {
  return new Promise((resolve) => {
    setTimeout(() => resolve([{ id: 1, total: 100 }]), 100);
  });
}

// 链式调用,每个 then 接收上一个 then 的返回值
fetchUser(1)
  .then(user => {
    console.log(user); // { id: 1, name: 'Alice' }
    return fetchOrders(user); // 返回新的 Promise
  })
  .then(orders => {
    console.log(orders); // [{ id: 1, total: 100 }]
    return orders.length;
  })
  .then(count => {
    console.log(`共 ${count} 笔订单`);
  });

如果 then 中返回一个 Promise,链条会自动等待它完成。

Promise 组合 API

Promise.all —— 全部完成

等待所有 Promise 全部成功,任一失败则整体失败:

javascript
const p1 = fetch('/api/users');
const p2 = fetch('/api/posts');
const p3 = fetch('/api/comments');

Promise.all([p1, p2, p3])
  .then(([users, posts, comments]) => {
    console.log('全部加载完成');
  })
  .catch(error => {
    console.error('任一请求失败:', error);
  });

Promise.race —— 竞速

返回最先完成的那个 Promise 的结果:

javascript
// 超时控制模式
const fetchWithTimeout = (url, timeout = 5000) => {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('请求超时')), timeout)
    )
  ]);
};

fetchWithTimeout('/api/data')
  .then(response => response.json())
  .catch(err => console.error(err.message));

Promise.allSettled —— 全部 settled

等待所有 Promise 完成(无论成功或失败),返回每个 Promise 的状态和结果:

javascript
const promises = [
  Promise.resolve('success'),
  Promise.reject('error'),
  Promise.resolve('another success')
];

Promise.allSettled(promises)
  .then(results => {
    console.log(results);
    // [
    //   { status: 'fulfilled', value: 'success' },
    //   { status: 'rejected', reason: 'error' },
    //   { status: 'fulfilled', value: 'another success' }
    // ]
  });

Promise.any —— 任一成功(ES2021)

返回最先成功的 Promise,全部失败则返回 AggregateError

javascript
const mirrors = [
  fetch('https://mirror-a.com/data'),
  fetch('https://mirror-b.com/data'),
  fetch('https://mirror-c.com/data')
];

Promise.any(mirrors)
  .then(response => response.json())
  .catch(err => console.error('所有镜像都失败了:', err.errors));

async/await 语法糖

async/await 让异步代码看起来像同步代码,是 Promise 的语法糖。

基本用法

javascript
// async 函数总是返回 Promise
async function getUserData() {
  const response = await fetch('/api/user');
  const user = await response.json();
  return user; // 自动包装为 Promise.resolve(user)
}

// 调用
getUserData().then(user => console.log(user));

await 会暂停 async 函数的执行,等待 Promise 完成,然后返回其结果。

与 Promise 等价转换

javascript
// async/await
async function loadData() {
  const a = await fetchA();
  const b = await fetchB(a.id);
  return b;
}

// 等价的 Promise 写法
function loadData() {
  return fetchA().then(a => {
    return fetchB(a.id);
  }).then(b => {
    return b;
  });
}

并行执行

await 默认是串行的,需要并行时先用 Promise.all

javascript
async function loadDashboard() {
  // 串行:慢!
  // const user = await fetchUser();
  // const posts = await fetchPosts();
  // const stats = await fetchStats();

  // 并行:快!
  const [user, posts, stats] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchStats()
  ]);

  return { user, posts, stats };
}

错误处理

try/catch/finally

javascript
async function updateProfile(data) {
  try {
    const response = await fetch('/api/profile', {
      method: 'POST',
      body: JSON.stringify(data)
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error('更新失败:', error.message);
    // 可以重新抛出或返回默认值
    throw error;
  } finally {
    // 无论成功失败都会执行
    console.log('请求结束');
  }
}

条件性捕获

javascript
async function fetchData() {
  try {
    return await fetch('/api/data');
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('请求被取消');
      return null;
    }
    throw err; // 其他错误继续向上抛
  }
}

async/await 与事件循环

理解 await 的执行时机对排查问题至关重要:

javascript
async function demo() {
  console.log('A');
  await Promise.resolve(); // 微任务,当前代码执行完后才恢复
  console.log('B');
}

console.log('C');
demo();
console.log('D');

// 输出顺序:C → A → D → B

执行过程:

  1. console.log('C')
  2. 进入 demo()console.log('A')
  3. 遇到 awaitdemo 暂停,将后续代码加入微任务队列
  4. console.log('D')
  5. 当前调用栈清空,执行微任务 → console.log('B')

常见模式与最佳实践

1. 睡眠函数

javascript
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function demo() {
  console.log('开始');
  await sleep(1000);
  console.log('1秒后');
}

2. 重试机制

javascript
async function retry(fn, maxAttempts = 3, delay = 1000) {
  for (let i = 1; i <= maxAttempts; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === maxAttempts) throw err;
      await sleep(delay);
    }
  }
}

const result = await retry(() => fetch('/api/flaky'), 3);

3. 串行执行数组

javascript
// 串行处理(保持顺序)
async function processInSequence(items, processor) {
  const results = [];
  for (const item of items) {
    results.push(await processor(item));
  }
  return results;
}

// 对比:并行处理
async function processInParallel(items, processor) {
  return Promise.all(items.map(processor));
}

4. 顶级 await(模块中)

javascript
// module.mjs
const data = await fetch('/api/config').then(r => r.json());
export { data };

顶级 await 仅在 ES Module 中可用,模块会等待异步操作完成后再导出。

小结

API用途返回条件
Promise.all全部成功全部 fulfilled
Promise.race竞速第一个 settled
Promise.allSettled等待全部全部 settled
Promise.any任一成功第一个 fulfilled

async/await 让异步代码更易读,但底层仍是 Promise。理解两者的等价关系和事件循环中的执行顺序,是写出可靠异步代码的基础。

#javascript #promise #async-await #异步编程 #事件循环

评论

A

Written by

AI-Writer

Related Articles

javascript
#19

测试与调试

掌握 Chrome DevTools 调试技巧、断言与异常处理、Vitest/Jest 单元测试框架,以及测试覆盖率与 TDD 实践。

Read More