学习前提

理解函数、回调和try/catch;先完成作用域章节。

本课完成后你能做什么

预测同步代码、Promise回调和定时器的执行顺序,并正确等待失败结果。

先理解机制

JavaScript 当前任务中的同步代码先执行完。Promise 的 then 与 await 后续通常通过微任务继续,微任务检查点会在进入下一任务前处理队列。setTimeout 的延迟是最早可调度时间,并不是准确执行时间;繁忙线程会推迟它。

Promise 处于pending、fulfilled或rejected,确定后不会再次变更结果。then 返回新的Promise,返回普通值会传给下一步,抛异常会让下一步进入拒绝状态。忘记return内部异步工作,会让外层链提前完成。

async函数总返回Promise,await暂停的是当前异步函数,不会阻塞整个网页。独立任务可以Promise.all并发等待;有依赖关系的任务需按顺序。Promise.all遇到拒绝会拒绝,但不会自动取消其他工作。

可运行示例

console.log("A");
setTimeout(() => console.log("D"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("B");
async function calculate() {
  try {
    const n = await Promise.resolve(21);
    return n * 2;
  } catch (error) {
    console.error(error);
    throw error;
  }
}
calculate().then(value => console.log(value));

推演与验证

预期A、B、C、42、D。前两项同步执行;then和await后续在定时器之前继续。将Promise.resolve(21)改成Promise.reject(new Error("demo")),并为最后调用加catch,观察拒绝如何向外传播。

常见错误与边界

forEach不会等待异步回调。需要串行用for...of加await,需要并行用map生成Promise数组再Promise.all。空catch会吞掉失败并把后续流程伪装成成功。

动手练习

创建两个延迟返回数字的任务,并发求和;让其中一个失败,确保只在成功时显示总和。

参考解法与理由

将Promise.all放在try中,成功后解构求和,catch单独显示失败。不要在finally里显示成功,finally只适合清理加载状态。

深入实验:异常必须沿Promise链传递

返回一个Promise和在回调里启动它但不返回,调用者看到的是不同的完成时机。先用完全本地的任务理解错误传播,再把同一结构应用到fetch。下面每次catch都返回替代值,便于观察恢复之后的链。

在现代浏览器Console中独立运行这个实验;它不替换上方主示例。

async function loadNumber(text) {
  const value = Number(text);
  if (text.trim() === '' || !Number.isFinite(value)) throw new Error('Invalid number');
  return value;
}
async function experiment() {
  console.log('start');
  const task = loadNumber('oops')
    .then(value => value * 2)
    .catch(error => { console.log(error.message); return 0; })
    .then(value => value + 1);
  console.log('scheduled');
  console.log(await task);
  console.log('end');
}
experiment();

预期观察

输出顺序是start、scheduled、Invalid number、1、end。失败跳过第一个then;catch返回0后,后面的then在成功路径执行。

原因、取舍与适用边界

catch不是只记录日志的装饰。如果记录错误后不重新throw,调用方可能认为任务成功,甚至把undefined当有效结果。finally适合清理loading状态,但不应return一个新的业务结果;其中抛出的异常还会替换原先结果。多个任务用Promise.all时,一个拒绝会让聚合Promise拒绝,但不会自动停止其他任务,它不是取消机制。

进阶练习

如果没有合理默认值,怎样记录错误又让调用者仍能显示失败状态?

解法与推理

在catch里记录后throw error,并在最外层await的try/catch里更新UI错误状态。为成功、失败和取消分别设定可观察状态,不要把所有异常都变成空数组,否则真实故障会伪装成“没有结果”。

进一步阅读