JavaScript异步编程:从回调到Async/Await

适用人群:前端开发者、Node.js开发者
目标:深入理解JavaScript异步编程机制
前置知识:JavaScript基础语法


为什么需要异步编程?

JavaScript是单线程语言,这意味着同一时间只能执行一个任务。如果所有操作都是同步的,那么耗时操作(如网络请求、文件读取)会阻塞整个程序,导致页面卡顿。

异步编程允许我们在等待耗时操作完成的同时,继续执行其他代码,大大提高了程序的效率和用户体验。


回调函数(Callback)

基本概念

回调函数是最早的异步编程方式,将一个函数作为参数传递给另一个函数,在操作完成后执行。

示例

// 同步操作
function synchronousOperation() {
console.log('开始');
console.log('执行中');
console.log('结束');
}
synchronousOperation();
// 输出:开始 -> 执行中 -> 结束
// 异步操作 - setTimeout
function 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有三种状态:

创建Promise

const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});
// 使用Promise
promise
.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 - 返回最先完成的Promise
Promise.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函数总是返回Promise
async 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

// 使用Promise
function 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请求

// 封装fetch
async 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链中使用catch
async 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开发的首选方式。

关键要点:

参考资源