异步编程(Promise / async-await)

从 callback 到 Promise 再到 async/await,系统讲解 JavaScript 异步编程的演进、Promise 状态机模型、链式调用原理及 async/await 的编译语义,助你在面试中展现对异步机制的深度理解。

为什么异步编程是前端核心能力

JavaScript 是单线程语言,所有 I/O 操作(网络请求、文件读取、定时器)都必须异步完成。异步编程能力直接决定了你能否写出高性能、无阻塞的应用代码。几乎每场前端面试都会涉及 Promise 和 async/await。


一、异步编程演进史

1.1 回调函数(Callback)

最原始的异步模式:

fs.readFile('a.txt', (err, dataA) => {
  if (err) throw err;
  fs.readFile('b.txt', (err, dataB) => {
    if (err) throw err;
    fs.readFile('c.txt', (err, dataC) => {
      // Callback Hell / Pyramid of Doom
    });
  });
});

问题:嵌套层级深(回调地狱)、错误处理分散、控制反转(Inversion of Control)——你把回调交给第三方,无法保证它被正确调用。

1.2 Promise(ES2015)

Promise 用状态机模型解决了回调的核心问题。

1.3 async/await(ES2017)

在 Promise 之上提供了同步风格的语法,是目前最主流的异步编程范式。


二、Promise 深入

2.1 三种状态

pending ──resolve(value)──→ fulfilled
   │
   └──reject(reason)──→ rejected
  • 状态只能从 pending 转为 fulfilledrejected
  • 一旦状态确定(settled),永远不可逆
  • resolve(anotherPromise) 会"锁定"到另一个 Promise 的状态(Resolution Procedure)

2.2 基本用法

const p = new Promise((resolve, reject) => {
  // executor 立即同步执行
  asyncOperation((err, data) => {
    if (err) reject(err);
    else resolve(data);
  });
});

p.then(onFulfilled, onRejected);

2.3 链式调用(Chaining)

.then() 总是返回一个新的 Promise,这是链式调用的基础:

fetch('/api/user')
  .then(res => res.json())          // 返回新 Promise
  .then(user => fetch(`/api/posts/${user.id}`))
  .then(res => res.json())
  .then(posts => console.log(posts))
  .catch(err => console.error(err))  // 捕获链中任何一步的错误
  .finally(() => hideLoading());     // 无论成功失败都执行

链式调用的值传递规则

.then(handler) 中 handler 的返回值 下一个 .then 接收到的值
普通值 x x
Promise.resolve(x) x(等待 resolve)
Promise.reject(err) 进入下一个 .catch
抛出异常 throw err 进入下一个 .catch
什么都不返回 undefined

2.4 Promise Resolution Procedure

resolve(value) 被调用时,如果 value 是一个 thenable(具有 .then 方法的对象),Promise 会"展开"它:

const p = new Promise(resolve => {
  resolve(Promise.resolve(42));
});

p.then(val => console.log(val)); // 42(不是 Promise 对象)

这就是 Promises/A+ 规范中定义的 Resolution Procedure,保证了 Promise 的可互操作性。

2.5 错误处理

// ✅ 推荐:在链末尾统一 catch
fetchData()
  .then(process)
  .then(save)
  .catch(handleError);

// ❌ 避免:在每个 then 中处理错误
fetchData()
  .then(process, handleError1)  // handleError1 无法捕获 process 中的错误
  .then(save, handleError2);

未处理的 rejection

// 浏览器和 Node.js 都会触发 unhandledrejection 事件
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled rejection:', event.reason);
});

三、Promise 静态方法

方法 行为 典型场景
Promise.all(iterable) 全部 fulfilled → fulfilled;任一 reject → reject 并行请求,全部成功才继续
Promise.allSettled(iterable) 等待全部 settled 并行请求,不关心个别失败
Promise.race(iterable) 第一个 settled 的结果 超时竞赛
Promise.any(iterable) 第一个 fulfilled 的结果;全部 reject → AggregateError 最快成功的结果
Promise.resolve(value) 包装为 fulfilled Promise 统一同步/异步接口
Promise.reject(reason) 包装为 rejected Promise 立即拒绝
Promise.withResolvers() 返回 { promise, resolve, reject } 替代 Deferred 模式(ES2024)

经典场景:请求超时

function fetchWithTimeout(url, ms) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), ms)
    )
  ]);
}

并发控制

// 限制并发数的 Promise 调度器
class Scheduler {
  #max;
  #running = 0;
  #queue = [];

  constructor(max) { this.#max = max; }

  add(promiseCreator) {
    return new Promise(resolve => {
      this.#queue.push(() => promiseCreator().then(resolve));
      this.#run();
    });
  }

  #run() {
    while (this.#running < this.#max && this.#queue.length) {
      const task = this.#queue.shift();
      this.#running++;
      task().finally(() => {
        this.#running--;
        this.#run();
      });
    }
  }
}

四、async/await 深入

4.1 本质:Generator + Promise 的语法糖

async 函数实际上是返回 Promise 的函数,await 是暂停执行等待 Promise resolve 的语法:

async function fetchUser() {
  const res = await fetch('/api/user');  // 暂停,等待 fetch resolve
  const user = await res.json();         // 暂停,等待 json() resolve
  return user;                           // 等价于 resolve(user)
}

// 等价的 Promise 写法
function fetchUser() {
  return fetch('/api/user')
    .then(res => res.json());
}

4.2 编译语义

从概念上,async/await 可以理解为 Generator 的自动执行:

// async/await
async function foo() {
  const a = await promise1;
  const b = await promise2;
  return a + b;
}

// 概念上等价于
function foo() {
  return spawn(function* () {
    const a = yield promise1;
    const b = yield promise2;
    return a + b;
  });
}

// spawn 是自动执行器(类似 co 库)
function spawn(genFn) {
  return new Promise((resolve, reject) => {
    const gen = genFn();
    function step(method, value) {
      try {
        const { done, value: next } = gen[method](value);
        if (done) resolve(next);
        else Promise.resolve(next).then(
          val => step('next', val),
          err => step('throw', err)
        );
      } catch (e) { reject(e); }
    }
    step('next', undefined);
  });
}

4.3 错误处理

// 方式一:try/catch(最常用)
async function load() {
  try {
    const data = await fetchData();
    return process(data);
  } catch (err) {
    console.error('Failed:', err);
  }
}

// 方式二:.catch() 在调用处处理
load().catch(handleError);

// 方式三:Go 风格的 tuple 解构
async function to(promise) {
  try {
    return [null, await promise];
  } catch (err) {
    return [err, null];
  }
}

const [err, data] = await to(fetchData());
if (err) handleError(err);

4.4 常见陷阱

串行 vs 并行

// ❌ 串行执行 — 总耗时 = t1 + t2
async function serial() {
  const a = await fetch('/api/a'); // 等待完成后
  const b = await fetch('/api/b'); // 才发起第二个请求
}

// ✅ 并行执行 — 总耗时 = max(t1, t2)
async function parallel() {
  const [a, b] = await Promise.all([
    fetch('/api/a'),
    fetch('/api/b')
  ]);
}

forEach 中的 await 不生效

// ❌ forEach 不会等待 async 回调
urls.forEach(async (url) => {
  await fetch(url); // 所有请求同时发出,不会按顺序
});

// ✅ 顺序执行用 for...of
for (const url of urls) {
  await fetch(url);
}

// ✅ 并行执行用 Promise.all + map
await Promise.all(urls.map(url => fetch(url)));

五、微任务与执行顺序

Promise.then/catch/finally 的回调以及 await 之后的代码都是作为 微任务(microtask) 执行的。这意味着它们在当前同步代码执行完毕后、下一个宏任务之前执行。

console.log('1');

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

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

console.log('4');

// 输出顺序:1, 4, 3, 2

详细的微任务/宏任务调度机制,参见 event-loop 专题。


六、面试高频题型

题型 1:Promise 输出顺序

Promise.resolve()
  .then(() => {
    console.log(0);
    return Promise.resolve(4);
  })
  .then(res => console.log(res));

Promise.resolve()
  .then(() => console.log(1))
  .then(() => console.log(2))
  .then(() => console.log(3));

// 输出:0, 1, 2, 3, 4
// 解释:return Promise.resolve(4) 需要额外两个微任务来"展开"

题型 2:实现 Promise.all

function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let settled = 0;
    const arr = Array.from(promises);

    if (arr.length === 0) return resolve([]);

    arr.forEach((p, i) => {
      Promise.resolve(p).then(
        value => {
          results[i] = value;
          if (++settled === arr.length) resolve(results);
        },
        reject // 任一失败立即 reject
      );
    });
  });
}

题型 3:实现带并发限制的批量请求

见第三节中的 Scheduler 实现。

题型 4:async 函数的返回值

async function foo() { return 1; }
async function bar() { return Promise.resolve(2); }

foo().then(console.log); // 1
bar().then(console.log); // 2(自动展开)

// async 函数总是返回 Promise
typeof foo(); // 'object'(Promise 实例)

七、手写 Promise(简化版)

class MyPromise {
  #state = 'pending';
  #value = undefined;
  #callbacks = [];

  constructor(executor) {
    const resolve = (value) => this.#transition('fulfilled', value);
    const reject = (reason) => this.#transition('rejected', reason);

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  #transition(state, value) {
    if (this.#state !== 'pending') return;
    this.#state = state;
    this.#value = value;
    queueMicrotask(() => this.#callbacks.forEach(cb => cb()));
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      const handle = () => {
        const handler = this.#state === 'fulfilled' ? onFulfilled : onRejected;
        const settle = this.#state === 'fulfilled' ? resolve : reject;

        if (typeof handler !== 'function') {
          settle(this.#value);
          return;
        }

        try {
          const result = handler(this.#value);
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (err) {
          reject(err);
        }
      };

      if (this.#state === 'pending') {
        this.#callbacks.push(handle);
      } else {
        queueMicrotask(handle);
      }
    });
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  finally(onFinally) {
    return this.then(
      value => MyPromise.resolve(onFinally()).then(() => value),
      reason => MyPromise.resolve(onFinally()).then(() => { throw reason; })
    );
  }

  static resolve(value) {
    if (value instanceof MyPromise) return value;
    return new MyPromise(resolve => resolve(value));
  }

  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason));
  }
}

八、与其他主题的关联

关联主题 关系
event-loop Promise 回调在微任务队列中执行,理解事件循环才能预测执行顺序
error-handling Promise rejection 的传播机制、unhandledrejection
iterators-generators async/await 的底层机制基于 Generator
scope-closure async 函数中的 stale closure 问题

参考资料

延展阅读