旧版“实习小结”把 Promise、async/await、Vuex 和 Ajax 混在一篇文章里,知识点很多但缺少主线。这里保留最有长期价值的主题:如何从状态机和事件循环理解 JavaScript 异步控制流。

一、Promise 是一次性状态机

Promise 只有 pendingfulfilledrejected 三种状态,且一旦落定不可再次变化。构造器中的函数会同步执行,then 注册的回调则进入微任务队列。

const task = new Promise((resolve) => {
  console.log('executor');
  resolve(42);
});

task.then(value => console.log(value));
console.log('sync end');
// executor → sync end → 42

二、await 后面的代码为什么“稍后执行”

await expression 会先把表达式转换为 Promise,然后暂停当前 async 函数;恢复执行的部分会被安排为微任务。因此它不会阻塞线程,只会让当前函数让出执行权。

async function demo() {
  console.log('A');
  await 0;
  console.log('B');
}

demo();
console.log('C');
// A → C → B

三、错误如何沿链路传播

then 回调抛出的异常会让返回的新 Promise 进入 rejected。catch 本质上是 then(undefined, onRejected)。在 async 函数中抛出异常,等价于返回一个 rejected Promise。

async function loadUser() {
  const response = await fetch('/api/user');
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

loadUser()
  .then(render)
  .catch(showError)
  .finally(stopLoading);

四、串行与并发要显式选择

连续 await 会串行执行。互不依赖的请求应先创建 Promise,再统一等待;同时要控制并发上限,避免一次性压垮服务端。

// 串行:总耗时约为两次请求之和
const user = await getUser();
const orders = await getOrders();

// 并发:总耗时约为较慢的那一次
const [user, orders] = await Promise.all([
  getUser(),
  getOrders()
]);

五、常见误区

理解 Promise 的关键不是记 API,而是理解状态只落定一次、回调通过微任务调度、每次 then 都返回一个新 Promise。