JavaScript 异步编程入门
JavaScript 是单线程的——同一时间只能做一件事。但网页需要同时处理很多事情:等待服务器响应、读取文件、定时提醒。异步编程让 JavaScript 在不阻塞页面的情况下处理这些耗时操作。
为什么需要异步
来看一个最简单的异步操作:setTimeout()
javascript
document.write('开始' + '<br>');
setTimeout(() => {
document.write('2 秒后执行' + '<br>');
}, 2000);
document.write('结束' + '<br>');
// 输出顺序:开始 → 结束 → 2 秒后执行 setTimeout 不会阻塞后面的代码——这就是异步。“开始”和”结束”立即打印,回调函数在 2 秒后才执行。
setTimeout() 与 setInterval()
javascript
// 延迟执行一次
const timeoutId = setTimeout(() => {
document.write('3 秒后执行一次' + '<br>');
}, 3000);
// 每秒重复执行,3 秒后自动停止
let count = 0;
const intervalId = setInterval(() => {
count++;
document.write('每秒执行一次' + '<br>');
if (count === 3) {
clearInterval(intervalId);
clearTimeout(timeoutId);
document.write('定时器已清除' + '<br>');
}
}, 1000); 回调函数
最初处理异步的方式是回调函数:
javascript
function fetchData(callback) {
setTimeout(() => {
const data = { name: '张三', age: 25 };
callback(data);
}, 1000);
}
fetchData((data) => {
document.write('收到数据:' + JSON.stringify(data) + '<br>');
}); 但当多个异步操作需要按顺序执行时,回调嵌套会迅速恶化:
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('成功了');
} else {
reject('失败了');
}
}, 1000);
});
promise
.then((result) => {
document.write('成功:' + result + '<br>');
})
.catch((error) => {
document.write('失败:' + error + '<br>');
})
.finally(() => {
document.write('无论成败都会执行' + '<br>');
}); 消费 Promise
javascript
promise
.then((result) => {
console.log('成功:', result);
})
.catch((error) => {
console.log('失败:', error);
})
.finally(() => {
console.log('无论成败都会执行');
}); Promise 链
then 可以链式调用,解决回调地狱:
javascript
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function fetchUser(id) {
return delay(500).then(() => ({ id, name: '张三' }));
}
function fetchPosts(userId) {
return delay(500).then(() => [{ id: 1, title: '文章1' }]);
}
function fetchComments(postId) {
return delay(500).then(() => [{ id: 1, text: '写得不错' }]);
}
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => document.write('评论:' + JSON.stringify(comments) + '<br>'))
.catch(error => document.write('出错:' + error + '<br>')); 扁平、可读、错误在链尾统一处理。
async / await — 异步同步写法
ES2017 引入的语法糖,让异步代码看起来像同步代码:
javascript
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function fetchUser(id) {
return delay(500).then(() => ({ id, name: '张三' }));
}
function fetchPosts(userId) {
return delay(500).then(() => [{ id: 1, title: '文章1' }]);
}
function fetchComments(postId) {
return delay(500).then(() => [{ id: 1, text: '写得不错' }]);
}
async function loadData() {
try {
const user = await fetchUser(1);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
document.write('评论:' + JSON.stringify(comments) + '<br>');
} catch (error) {
document.write('出错:' + error + '<br>');
}
}
loadData(); async标记函数为异步函数——它始终返回一个 Promiseawait等待一个 Promise 完成,获取其结果try...catch捕获错误,替代.catch()
async/await 是当前 JavaScript 异步编程的推荐方式——代码清晰易读,错误处理自然。await 只能在 async 函数内部使用;在顶层(模块顶层)也可以使用,现代浏览器都支持。
并行的异步操作
当多个异步操作互不依赖时,可以并行执行:
javascript
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function fetchUser(id) {
return delay(800).then(() => ({ id, name: '张三' }));
}
function fetchPosts(userId) {
return delay(800).then(() => [{ id: 1, title: '文章1' }]);
}
function fetchSettings(userId) {
return delay(800).then(() => ({ theme: 'dark' }));
}
async function compare() {
const t1 = Date.now();
const user = await fetchUser(1);
const posts = await fetchPosts(2);
const settings = await fetchSettings(3);
document.write('串行耗时:' + (Date.now() - t1) + 'ms' + '<br>');
const t2 = Date.now();
const [user2, posts2, settings2] = await Promise.all([
fetchUser(1),
fetchPosts(2),
fetchSettings(3),
]);
document.write('并行耗时:' + (Date.now() - t2) + 'ms' + '<br>');
}
compare(); 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 章节中进一步展开。