JavaScript 异步编程入门
JavaScript 是单线程的——同一时间只能做一件事。但网页需要同时处理很多事情:等待服务器响应、读取文件、定时提醒。异步编程让 JavaScript 在不阻塞页面的情况下处理这些耗时操作。
为什么需要异步
来看一个最简单的异步操作:setTimeout()
javascript
console.log('开始');
setTimeout(() => {
console.log('2 秒后执行');
}, 2000);
console.log('结束');
// 输出顺序:开始 → 结束 → 2 秒后执行 setTimeout 不会阻塞后面的代码——这就是异步。“开始”和”结束”立即打印,回调函数在 2 秒后才执行。
setTimeout() 与 setInterval()
javascript
// 延迟执行一次
const timeoutId = setTimeout(() => {
console.log('3 秒后我只执行一次');
}, 3000);
// 每隔一段时间重复执行
const intervalId = setInterval(() => {
console.log('每秒执行一次');
}, 1000);
// 停止定时器
clearTimeout(timeoutId); // 取消 setTimeout
clearInterval(intervalId); // 取消 setInterval 回调函数
最初处理异步的方式是回调函数:
javascript
function fetchData(callback) {
setTimeout(() => {
const data = { name: '张三', age: 25 };
callback(data);
}, 1000);
}
fetchData((data) => {
console.log('收到数据:', data);
}); 但当多个异步操作需要按顺序执行时,回调嵌套会迅速恶化:
javascript
// ❌ 回调地狱(Callback Hell)
step1((result1) => {
step2(result1, (result2) => {
step3(result2, (result3) => {
step4(result3, (result4) => {
console.log('终于完成');
});
});
});
}); 这就是为什么 Promise 被发明了。
Promise — 承诺
Promise 代表一个未来会完成的异步操作。它有三种状态:
- pending(进行中)— 初始状态
- fulfilled(已成功)— 操作成功
- rejected(已失败)— 操作失败
创建 Promise
javascript
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('成功了'); // 将 Promise 标记为 fulfilled
} else {
reject('失败了'); // 将 Promise 标记为 rejected
}
}, 1000);
}); 消费 Promise
javascript
promise
.then((result) => {
console.log('成功:', result);
})
.catch((error) => {
console.log('失败:', error);
})
.finally(() => {
console.log('无论成败都会执行');
}); Promise 链
then 可以链式调用,解决回调地狱:
javascript
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log('评论:', comments))
.catch(error => console.log('出错:', error)); 扁平、可读、错误在链尾统一处理。
async / await — 异步同步写法
ES2017 引入的语法糖,让异步代码看起来像同步代码:
javascript
async function loadData() {
try {
const user = await fetchUser(1);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
console.log('评论:', comments);
} catch (error) {
console.log('出错:', error);
}
}
loadData(); async标记函数为异步函数——它始终返回一个 Promiseawait等待一个 Promise 完成,获取其结果try...catch捕获错误,替代.catch()
async/await 是当前 JavaScript 异步编程的推荐方式——代码清晰易读,错误处理自然。await 只能在 async 函数内部使用;在顶层(模块顶层)也可以使用,现代浏览器都支持。
并行的异步操作
当多个异步操作互不依赖时,可以并行执行:
javascript
// 串行(总时间 = 三个耗时之和)
const user = await fetchUser(1);
const posts = await fetchPosts(2);
const settings = await fetchSettings(3);
// 并行(总时间 = 最长的那个)
const [user, posts, settings] = await Promise.all([
fetchUser(1),
fetchPosts(2),
fetchSettings(3),
]); Promise.all 是”全部成功或一个失败”——只要其中一个 Promise 被 reject,整个 Promise.all 立即失败。如果需要”部分成功也可以”的语义,使用 Promise.allSettled。
fetch() — 发送网络请求
fetch() 是浏览器内置的 HTTP 请求方法,返回 Promise:
javascript
async function getUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error(`请求失败:${response.status}`);
}
const data = await response.json(); // 解析 JSON
return data;
} fetch 的详细用法在 JSON 章节中进一步展开。