旧版“实习小结”把 Promise、async/await、Vuex 和 Ajax 混在一篇文章里,知识点很多但缺少主线。这里保留最有长期价值的主题:如何从状态机和事件循环理解 JavaScript 异步控制流。
一、Promise 是一次性状态机
Promise 只有 pending、fulfilled、rejected 三种状态,且一旦落定不可再次变化。构造器中的函数会同步执行,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()
]);
五、常见误区
- 忘记 return Promise,导致链路提前完成。
- 在
forEach中使用 async,却没有等待所有任务。 - 使用
Promise.all时忽略“任一失败则整体失败”的语义。 - 把 await 理解为线程阻塞。
- 吞掉异常后不记录上下文,使错误无法定位。
理解 Promise 的关键不是记 API,而是理解状态只落定一次、回调通过微任务调度、每次 then 都返回一个新 Promise。