📚 现代前端开发 目录
阶段1:Web基础
第 7 / 35 课

异步编程

从回调地狱到async/await

📖 核心概念

💻 代码实现

Promise与async/await ✅ javascript
// Promise基础 - 封装API请求
function request(url, options = {}) {
  return new Promise((resolve, reject) => {
    const controller = new AbortController();
    options.signal = controller.signal;

    // 5秒超时
    const timeout = setTimeout(() => controller.abort(), 5000);

    fetch(url, options)
      .then(res => {
        clearTimeout(timeout);
        if (!res.ok) throw new Error(\`HTTP \${res.status}\`);
        return res.json();
      })
      .then(resolve)
      .catch(reject);
  });
}

// async/await - 优雅的异步代码
async function loadUserProfile(userId) {
  try {
    const [profile, posts, followers] = await Promise.all([
      request(\`/api/users/\${userId}\`),
      request(\`/api/users/\${userId}/posts\`),
      request(\`/api/users/\${userId}/followers\`),
    ]);

    return { profile, posts, followers };
  } catch (error) {
    if (error.name === 'AbortError') {
      console.error('请求被取消或超时');
    } else {
      console.error('加载失败:', error.message);
    }
    throw error;
  }
}

// Promise组合器
async function loadDashboard() {
  // all: 全部成功才成功
  const essential = await Promise.all([
    request('/api/user'),
    request('/api/config'),
  ]);

  // allSettled: 无论成败都等
  const optional = await Promise.allSettled([
    request('/api/notifications'),
    request('/api/recommendations'),
    request('/api/analytics'),
  ]);

  const succeeded = optional
    .filter(r => r.status === 'fulfilled')
    .map(r => r.value);

  return { essential, optional: succeeded };
}

// 重试机制
async function retry(fn, maxRetries = 3, delay = 1000) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise(r => setTimeout(r, delay * (i + 1)));
      console.log(\`重试第 \${i + 1} 次...\`);
    }
  }
}
异步迭代与并发控制 ✅ javascript
// 异步迭代器
async function* fetchPages(baseUrl) {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await request(\`\${baseUrl}?page=\${page}\`);
    yield response.data;
    hasMore = response.hasNext;
    page++;
  }
}

// 使用 for await...of
async function loadAllPosts() {
  const allPosts = [];
  for await (const posts of fetchPages('/api/posts')) {
    allPosts.push(...posts);
  }
  return allPosts;
}

// 并发控制 - 限制同时进行的请求数
async function concurrentLimit(tasks, limit) {
  const results = [];
  const executing = new Set();

  for (const [i, task] of tasks.entries()) {
    const p = task().then(result => {
      executing.delete(p);
      return result;
    });
    executing.add(p);
    results[i] = p;

    if (executing.size >= limit) {
      await Promise.race(executing);
    }
  }

  return Promise.all(results);
}

// 使用:最多3个并发请求
const urls = Array.from({ length: 10 }, (_, i) =>
  () => request(\`/api/item/\${i}\`)
);
const items = await concurrentLimit(urls, 3);

// 可取消的异步操作
class CancellationToken {
  #aborted = false;
  #listeners = new Set();

  get aborted() { return this.#aborted; }

  abort() {
    this.#aborted = true;
    this.#listeners.forEach(fn => fn());
  }

  onAbort(fn) {
    this.#listeners.add(fn);
  }
}

async function longOperation(token) {
  for (let i = 0; i < 100; i++) {
    if (token.aborted) throw new Error('操作已取消');
    await new Promise(r => setTimeout(r, 100));
  }
  return '完成';
}

const token = new CancellationToken();
setTimeout(() => token.abort(), 3000); // 3秒后取消
try {
  await longOperation(token);
} catch (e) {
  console.log(e.message); // "操作已取消"
}

🔍 异步编程陷阱与对策

常见陷阱:忘记await导致Promise未解析、在循环中使用await而非Promise.all、未处理的Promise rejection、async构造函数问题。最佳实践:始终使用try/catch包裹await、使用Promise.all处理并行异步、全局注册unhandledrejection事件处理。

🎯 练习任务

🏆 成就解锁
异步高手 — 掌握JavaScript异步编程全链路,写出优雅高效的异步代码