13JavaScript高级程序设计(第4版)

异步JavaScript

异步编程

Promiseasync/await事件循环

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第13章:异步JavaScript(Asynchronous JavaScript)

一、导读

1.1 本章学习目标

第13章介绍JavaScript的异步编程。本章核心目标:

  • 理解同步和异步的区别
  • 掌握回调函数和回调地狱
  • 深入理解Promise:状态、值、链式调用
  • 熟练使用async/await
  • 理解事件循环:宏任务和微任务

1.2 为什么本章如此重要

JavaScript是单线程语言,异步编程是处理耗时操作的关键。Promise和async/await是现代JavaScript异步编程的标准方式。理解事件循环的工作原理,才能写出正确的异步代码。

1.3 核心问题引导

JavaScript是单线程的,它是如何处理并发的?

Promise的三种状态是什么?状态转换是不可逆的吗?

async/await相比Promise有什么优势?

微任务和宏任务的执行顺序是什么?


二、核心概念详解

13.1 回调函数

javascript
// 同步回调
[1, 2, 3].forEach(x => console.log(x));

// 异步回调
setTimeout(() => {
  console.log("This runs after 1 second");
}, 1000);

// 回调地狱
getUser(userId, (user) => {
  getOrders(user.id, (orders) => {
    getOrderDetails(orders[0].id, (details) => {
      getProductInfo(details.productId, (product) => {
        console.log(product);
        // 层层嵌套,难以维护
      });
    });
  });
});

13.2 Promise

javascript
// 创建Promise
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let success = true;
      if (success) {
        resolve("Data loaded");
      } else {
        reject("Error occurred");
      }
    }, 1000);
  });
}

// 使用Promise
fetchData()
  .then(data => {
    console.log(data);
    return "processed";
  })
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("Done"));

// Promise组合
let p1 = Promise.resolve(1);
let p2 = Promise.resolve(2);
let p3 = Promise.resolve(3);

// Promise.all:所有都成功
Promise.all([p1, p2, p3]).then(values => {
  console.log(values);  // [1, 2, 3]
});

// Promise.race:最快的
Promise.race([p1, p2, p3]).then(value => {
  console.log(value);  // 1
});

// Promise.allSettled:等待所有完成
Promise.allSettled([
  Promise.resolve(1),
  Promise.reject("error"),
  Promise.resolve(3)
]).then(results => {
  console.log(results);
  // [{status: "fulfilled", value: 1}, 
  //  {status: "rejected", reason: "error"},
  //  {status: "fulfilled", value: 3}]
});

// Promise.any:第一个成功的
Promise.any([
  Promise.reject("error1"),
  Promise.resolve(2),
  Promise.resolve(3)
]).then(value => console.log(value));  // 2

13.3 async/await

javascript
// async函数总是返回Promise
async function getData() {
  return "data";  // 等价于 Promise.resolve("data")
}

getData().then(console.log);

// await等待Promise完成
async function loadData() {
  try {
    let data = await fetchData();
    console.log(data);
    
    let moreData = await fetchData();
    console.log(moreData);
  } catch (error) {
    console.error("Error:", error);
  }
}

// 并行执行
async function loadAll() {
  let [data1, data2, data3] = await Promise.all([
    fetchData(),
    fetchData(),
    fetchData()
  ]);
  console.log(data1, data2, data3);
}

// 顺序执行vs并行执行
async function sequential() {
  const start = Date.now();
  const a = await delay(1000);  // 1秒
  const b = await delay(1000);  // 又1秒
  console.log(`Sequential: ${Date.now() - start}ms`);  // ~2000ms
}

async function parallel() {
  const start = Date.now();
  const [a, b] = await Promise.all([
    delay(1000),
    delay(1000)
  ]);
  console.log(`Parallel: ${Date.now() - start}ms`);  // ~1000ms
}

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

13.4 事件循环

javascript
// 执行顺序
console.log("1");  // 同步

setTimeout(() => {
  console.log("2");  // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log("3");  // 微任务
});

console.log("4");  // 同步

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

// 微任务:Promise.then, process.nextTick, MutationObserver
// 宏任务:setTimeout, setInterval, I/O, UI rendering

// 微任务在当前宏任务结束后立即执行
// 宏任务在下一个事件循环周期执行

// 实际例子
function processQueue() {
  const queue = [];
  
  return {
    add(task) {
      queue.push(task);
      // 使用微任务确保所有同步代码先执行
      Promise.resolve().then(() => {
        while (queue.length > 0) {
          const task = queue.shift();
          task();
        }
      });
    }
  };
}

三、实战案例

3.1 带重试的异步请求

javascript
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      return await response.json();
    } catch (error) {
      console.log(`Attempt ${attempt} failed: ${error.message}`);
      if (attempt === maxRetries) throw error;
      
      // 指数退避
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// 使用
async function loadUserData(userId) {
  try {
    const user = await fetchWithRetry(`/api/users/${userId}`);
    console.log(user);
  } catch (error) {
    console.error("Failed to load user after all retries:", error);
  }
}

// 带超时的Promise
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) => {
    setTimeout(() => reject(new Error("Timeout")), ms);
  });
  return Promise.race([promise, timeout]);
}

// 使用
async function loadData() {
  try {
    const data = await withTimeout(fetchData(), 5000);
    console.log(data);
  } catch (error) {
    console.error(error.message);
  }
}

四、本章小结

第13章介绍了异步JavaScript:回调函数、Promise、async/await、事件循环。Promise和async/await是现代异步编程的标准方式。事件循环决定了代码的执行顺序。


五、思考与练习

思考题

Promise的链式调用是如何工作的?

async/await的错误处理有什么注意事项?

如何取消一个正在执行的Promise?

Web Workers和事件循环有什么关系?

编程练习

实现一个带并发限制的异步任务调度器。

实现一个Promise.all的polyfill。

使用async/await重构回调地狱代码。


13/14异步Ja
0%