🎯 课程目标

完成本课程后,你将能够:

  • 理解同步与异步编程的区别
  • 掌握Promise的使用方法和原理
  • 熟练使用async/await语法糖
  • 能够处理异步操作中的错误
  • 理解事件循环和宏任务/微任务

📖 异步编程概述

JavaScript是单线程语言,同步代码会阻塞程序执行。异步编程允许我们执行耗时操作(如网络请求、文件读取)时不阻塞主线程,提高程序的响应性和性能。

✨ 为什么需要异步?

  • 避免阻塞:网络请求可能需要几秒,不能让页面卡死
  • 提高性能:同时发起多个请求,充分利用时间
  • 改善体验:用户可以继续操作,无需等待完成
  • 定时任务:实现延迟执行和定时功能

⏰ 回调函数

回调是最基础的异步实现方式,将函数作为参数传递给异步操作。

// 定时器回调 console.log('开始'); setTimeout(() => { console.log('2秒后执行'); }, 2000); console.log('结束'); // 输出: 开始 -> 结束 -> 2秒后执行 // 数组遍历的回调 [1, 2, 3].forEach((item, index) => { console.log(item); }); // 事件处理回调 document.querySelector('button').addEventListener('click', (e) => { console.log('按钮被点击'); }); // 回调地狱(Callback Hell) // 不推荐:多层嵌套的回调难以维护 fetchData((user) => { getPosts(user.id, (posts) => { getComments(posts[0].id, (comments) => { console.log(comments); }); }); });

🤝 Promise

Promise是ES6引入的异步编程解决方案,用于表示一个异步操作的最终完成或失败。

创建Promise

// 创建Promise const myPromise = new Promise((resolve, reject) => { // 异步操作 setTimeout(() => { const success = true; if (success) { resolve('操作成功'); // 成功时调用 } else { reject('操作失败'); // 失败时调用 } }, 1000); }); // 使用Promise myPromise .then(result => { console.log(result); // '操作成功' }) .catch(error => { console.error(error); // '操作失败' }) .finally(() => { console.log('无论成功失败都会执行'); });

Promise状态

// Promise有三种状态: // 1. pending(进行中) // 2. fulfilled(已成功) // 3. rejected(已失败) const promise = new Promise((resolve, reject) => { // 初始状态是 pending console.log('Promise创建'); setTimeout(() => { // 可以从pending变为fulfilled或rejected resolve('成功'); // 或者 reject('失败'); // 注意:状态一旦改变就不会再变 }, 1000); }); console.log('代码执行'); // 输出: Promise创建 -> 代码执行 -> 成功

Promise链式调用

// 链式调用是Promise的强大之处 fetch('/api/data') .then(response => response.json()) .then(data => { console.log(data); return processData(data); }) .then(processed => { console.log(processed); }) .catch(error => { console.error('任何环节出错都会到这里'); }); // 返回Promise的then会自动被链式调用等待 function asyncOperation1() { return new Promise(resolve => setTimeout(() => resolve(1), 1000)); } function asyncOperation2(value) { return new Promise(resolve => setTimeout(() => resolve(value * 2), 1000)); } asyncOperation1() .then(result => asyncOperation2(result)) .then(finalResult => console.log(finalResult)); // 2

Promise静态方法

// Promise.resolve() - 创建已解决的Promise Promise.resolve('成功').then(console.log); // Promise.reject() - 创建已拒绝的Promise Promise.reject('失败').catch(console.error); // Promise.all() - 等待所有Promise完成 const p1 = fetch('/api/users'); const p2 = fetch('/api/posts'); const p3 = fetch('/api/comments'); Promise.all([p1, p2, p3]) .then(([usersRes, postsRes, commentsRes]) => { return Promise.all([ usersRes.json(), postsRes.json(), commentsRes.json() ]); }) .then(([users, posts, comments]) => { console.log(users, posts, comments); }) .catch(error => { console.error('任何一个失败都会触发'); }); // Promise.race() - 返回最先完成的结果 Promise.race([ fetch('/api/fast'), new Promise(resolve => setTimeout(resolve, 5000)) ]).then(response => console.log('最快的那个')); // Promise.allSettled() - 等待所有Promise结束(无论成功失败) Promise.allSettled([p1, p2, p3]).then(results => { results.forEach(result => { if (result.status === 'fulfilled') { console.log('成功:', result.value); } else { console.log('失败:', result.reason); } }); });

⚡ async/await

async/await是Promise的语法糖,让异步代码看起来像同步代码,更容易理解和维护。

基本用法

// async函数自动返回Promise async function fetchData() { const response = await fetch('/api/data'); const data = await response.json(); return data; } // 调用async函数 fetchData().then(data => console.log(data)); // 也可以使用await等待结果(需要在async函数中或模块顶层) // const data = await fetchData();

错误处理

async function fetchUserData(userId) { try { const response = await fetch(`/api/users/${userId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const user = await response.json(); return user; } catch (error) { console.error('获取用户数据失败:', error); throw error; // 可以重新抛出错误 } } // 或者使用catch fetchUserData(123) .then(user => console.log(user)) .catch(error => console.error(error));

并行执行

// 顺序执行(一个接一个) async function sequential() { const a = await fetch('/api/a').then(r => r.json()); const b = await fetch('/api/b').then(r => r.json()); const c = await fetch('/api/c').then(r => r.json()); return { a, b, c }; } // 并行执行(同时发起) async function parallel() { const [a, b, c] = await Promise.all([ fetch('/api/a').then(r => r.json()), fetch('/api/b').then(r => r.json()), fetch('/api/c').then(r => r.json()) ]); return { a, b, c }; } // 处理部分失败 async function withSomeFailures() { const results = await Promise.allSettled([ fetch('/api/a').then(r => r.json()), fetch('/api/b').then(r => r.json()), fetch('/api/c').then(r => r.json()) ]); return results.map(r => r.status === 'fulfilled' ? r.value : null); }

🔄 事件循环

理解事件循环有助于理解异步代码的执行顺序。

console.log('1'); // 同步代码,立即执行 setTimeout(() => { console.log('2'); // 宏任务,延迟执行 }, 0); Promise.resolve().then(() => { console.log('3'); // 微任务,在同步代码后、宏任务前执行 }); requestAnimationFrame(() => { console.log('4'); // 浏览器渲染任务 }); console.log('5'); // 执行顺序: // 1 -> 5 -> 3 -> 4 -> 2 // 因为Promise.then是微任务,会在下一个宏任务之前执行
💡 执行顺序:同步代码 → 微任务(Promise)→ 渲染 → 宏任务(setTimeout、setInterval)

⚠️ 常见问题

问题原因分析解决方案
await在非async函数中使用只能在async函数中使用将代码包装在async函数中
Promise状态已改变后继续等待resolve/reject只能调用一次确保只调用一次
forEach中的异步不等待forEach不会等待回调完成使用for...of循环
并发请求过多没有限制并发数量使用Promise池或批处理
内存泄漏未清理的事件监听器在组件卸载时清理

✅ 课后练习

练习要求:请独立完成以下练习任务:

  1. 练习 1:使用fetch API和async/await获取并显示多篇文章
  2. 练习 2:实现一个请求队列,控制并发数量
  3. 选做练习:实现一个带重试机制的请求函数