JavaScript异步编程:从回调到Async/Await
适用人群:前端开发者、Node.js开发者
目标:深入理解JavaScript异步编程机制
前置知识:JavaScript基础语法
为什么需要异步编程?
JavaScript是单线程语言,这意味着同一时间只能执行一个任务。如果所有操作都是同步的,那么耗时操作(如网络请求、文件读取)会阻塞整个程序,导致页面卡顿。
异步编程允许我们在等待耗时操作完成的同时,继续执行其他代码,大大提高了程序的效率和用户体验。
回调函数(Callback)
基本概念
回调函数是最早的异步编程方式,将一个函数作为参数传递给另一个函数,在操作完成后执行。
示例
// 同步操作function synchronousOperation() { console.log('开始'); console.log('执行中'); console.log('结束');}synchronousOperation();// 输出:开始 -> 执行中 -> 结束
// 异步操作 - setTimeoutfunction asynchronousOperation() { console.log('开始'); setTimeout(() => { console.log('执行中'); }, 1000); console.log('结束');}asynchronousOperation();// 输出:开始 -> 结束 -> 执行中(1秒后)回调地狱(Callback Hell)
当多个异步操作需要按顺序执行时,会出现多层嵌套,形成”回调地狱”:
// 回调地狱示例getData(function(a) { getMoreData(a, function(b) { getMoreData(b, function(c) { getMoreData(c, function(d) { getMoreData(d, function(e) { // 嵌套太深,难以维护 console.log(e); }); }); }); });});Promise
基本概念
Promise是ES6引入的异步编程解决方案,它代表一个异步操作的最终完成(或失败)及其结果值。
Promise有三种状态:
- Pending(进行中):初始状态
- Fulfilled(已成功):操作成功完成
- Rejected(已失败):操作失败
创建Promise
const promise = new Promise((resolve, reject) => { // 异步操作 setTimeout(() => { const success = true; if (success) { resolve('操作成功'); } else { reject('操作失败'); } }, 1000);});
// 使用Promisepromise .then(result => { console.log(result); // 操作成功 }) .catch(error => { console.error(error); }) .finally(() => { console.log('操作完成'); });Promise链式调用
// 链式调用解决回调地狱getData() .then(a => getMoreData(a)) .then(b => getMoreData(b)) .then(c => getMoreData(c)) .then(d => getMoreData(d)) .then(e => console.log(e)) .catch(error => console.error(error));Promise静态方法
// Promise.all - 所有Promise都成功才成功Promise.all([promise1, promise2, promise3]) .then(results => { console.log(results); // [result1, result2, result3] }) .catch(error => { console.error(error); });
// Promise.race - 返回最先完成的PromisePromise.race([promise1, promise2]) .then(result => { console.log(result); // 最先完成的Promise的结果 });
// Promise.allSettled - 等待所有Promise完成(无论成功失败)Promise.allSettled([promise1, promise2]) .then(results => { results.forEach(result => { if (result.status === 'fulfilled') { console.log('成功:', result.value); } else { console.log('失败:', result.reason); } }); });
// Promise.any - 任一Promise成功就成功Promise.any([promise1, promise2, promise3]) .then(result => { console.log(result); // 第一个成功的Promise的结果 }) .catch(error => { console.error('所有Promise都失败了'); });Async/Await
基本概念
Async/Await是ES2017引入的语法糖,让异步代码看起来像同步代码,基于Promise实现,使代码更易读。
async函数
// async函数总是返回Promiseasync function myFunction() { return 'Hello'; // 等同于 return Promise.resolve('Hello')}
myFunction().then(result => { console.log(result); // Hello});await表达式
// await只能在async函数内使用async function fetchData() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log(data); } catch (error) { console.error('获取数据失败:', error); }}
fetchData();对比Promise和Async/Await
// 使用Promisefunction fetchUserData() { return fetch('/api/user') .then(response => response.json()) .then(user => { return fetch(`/api/posts/${user.id}`); }) .then(response => response.json()) .then(posts => { console.log(posts); }) .catch(error => { console.error(error); });}
// 使用Async/Await(更清晰)async function fetchUserData() { try { const userResponse = await fetch('/api/user'); const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts/${user.id}`); const posts = await postsResponse.json();
console.log(posts); } catch (error) { console.error(error); }}并行执行
// 串行执行(一个接一个)async function serial() { const a = await fetch('/api/a'); const b = await fetch('/api/b'); const c = await fetch('/api/c'); return [a, b, c];}
// 并行执行(同时发起)async function parallel() { const [a, b, c] = await Promise.all([ fetch('/api/a'), fetch('/api/b'), fetch('/api/c') ]); return [a, b, c];}实际应用示例
封装AJAX请求
// 封装fetchasync function request(url, options = {}) { try { const response = await fetch(url, { headers: { 'Content-Type': 'application/json', ...options.headers }, ...options });
if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); }
return await response.json(); } catch (error) { console.error('请求失败:', error); throw error; }}
// 使用async function getUser(id) { const user = await request(`/api/users/${id}`); return user;}文件读取(Node.js)
const fs = require('fs').promises;
async function readFile(path) { try { const data = await fs.readFile(path, 'utf8'); console.log(data); } catch (error) { console.error('读取文件失败:', error); }}数据库操作
async function getUserWithPosts(userId) { const connection = await db.getConnection();
try { await connection.beginTransaction();
const user = await connection.query( 'SELECT * FROM users WHERE id = ?', [userId] );
const posts = await connection.query( 'SELECT * FROM posts WHERE user_id = ?', [userId] );
await connection.commit();
return { user, posts }; } catch (error) { await connection.rollback(); throw error; } finally { connection.release(); }}错误处理
try-catch
async function handleData() { try { const data = await fetchData(); const processed = await processData(data); return processed; } catch (error) { console.error('处理数据时出错:', error); // 可以选择重新抛出错误 throw error; }}.catch()
// 在Promise链中使用catchasync function handleData() { const data = await fetchData().catch(error => { console.error('获取数据失败:', error); return null; // 返回默认值 });
if (data) { return processData(data); }}最佳实践
1. 总是处理错误
// 不好的做法async function bad() { const data = await fetchData(); // 可能抛出未捕获的错误}
// 好的做法async function good() { try { const data = await fetchData(); return data; } catch (error) { console.error(error); throw error; }}2. 合理使用并行
// 不好的做法 - 不必要的串行async function bad() { const user = await fetchUser(); const products = await fetchProducts(); // 不依赖user const categories = await fetchCategories(); // 不依赖user}
// 好的做法 - 并行执行async function good() { const [user, products, categories] = await Promise.all([ fetchUser(), fetchProducts(), fetchCategories() ]);}3. 避免async构造函数
// 不好的做法class Bad { constructor() { this.data = await fetchData(); // 语法错误 }}
// 好的做法class Good { constructor() { this.dataPromise = fetchData(); }
async getData() { return await this.dataPromise; }}总结
JavaScript异步编程经历了从回调函数到Promise,再到Async/Await的演进。Async/Await让异步代码更易读、更易维护,是现代JavaScript开发的首选方式。
关键要点:
- 避免回调地狱,使用Promise或Async/Await
- 合理使用Promise.all进行并行操作
- 总是处理异步操作的错误
- 理解事件循环机制,避免阻塞主线程