1.5 异步编程
JavaScript 是单线程语言,异步编程是处理耗时操作(如网络请求、文件读取)的核心机制。
一、同步与异步
同步执行
javascript
console.log("1");
console.log("2");
console.log("3");
// 输出:1, 2, 3(按顺序执行)异步执行
javascript
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
console.log("3");
// 输出:1, 3, 2(setTimeout 是异步的,放入任务队列)事件循环
┌─────────────────────┐
│ 执行栈(同步代码) │
└──────────┬──────────┘
│
↓ 完成后
┌─────────────────────┐
│ 任务队列(异步代码) │ ← 浏览器 API 调用后放入
└─────────────────────┘
│
↓ 栈空闲时
执行任务队列中的任务二、回调函数(Callback)
基本用法
javascript
function getData(callback) {
setTimeout(() => {
const data = { name: "张三", age: 25 };
callback(data);
}, 1000);
}
function handleData(data) {
console.log(data);
}
getData(handleData);回调地狱
javascript
// 层层嵌套,难以维护
getData1(data1 => {
getData2(data2 => {
getData3(data3 => {
getData4(data4 => {
// ... 越来越深
});
});
});
});错误处理
javascript
function getData(successCallback, errorCallback) {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
successCallback({ name: "张三" });
} else {
errorCallback("获取数据失败");
}
}, 1000);
}
getData(
data => console.log(data),
error => console.error(error)
);三、Promise(ES6)
基本用法
javascript
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve({ name: "张三" });
} else {
reject("失败");
}
}, 1000);
});
// 使用
promise
.then(data => {
console.log("成功:", data);
})
.catch(error => {
console.error("失败:", error);
})
.finally(() => {
console.log("无论如何都会执行");
});链式调用
javascript
getData1()
.then(data1 => {
console.log(data1);
return getData2(); // 返回新的 Promise
})
.then(data2 => {
console.log(data2);
return getData3();
})
.then(data3 => {
console.log(data3);
})
.catch(error => {
console.error(error);
});Promise 静态方法
javascript
// Promise.all(全部成功才成功)
Promise.all([promise1, promise2, promise3])
.then(([result1, result2, result3]) => {
console.log("全部成功");
});
// Promise.race(第一个完成的结果)
Promise.race([promise1, promise2])
.then(result => {
console.log("最快的:", result);
});
// Promise.allSettled(全部完成,无论成功失败)
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.any([promise1, promise2])
.then(result => {
console.log("第一个成功的:", result);
});四、async/await(ES2017)
基本用法
javascript
async function fetchData() {
try {
const data = await getData();
console.log(data);
} catch (error) {
console.error(error);
}
}
fetchData();多个异步操作
javascript
// 顺序执行(串行)
async function sequential() {
const data1 = await getData1();
const data2 = await getData2(data1);
const data3 = await getData3(data2);
}
// 并行执行(推荐)
async function parallel() {
const [data1, data2] = await Promise.all([
getData1(),
getData2()
]);
}错误处理
javascript
// try-catch
async function handleData() {
try {
const data = await getData();
return data;
} catch (error) {
console.error(error);
return null; // 返回默认值
}
}
// Promise.catch()
getData()
.then(data => console.log(data))
.catch(error => console.error(error));五、常见异步场景
1. 网络请求(Fetch API)
javascript
// Promise 方式
fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
// async/await 方式
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);
}
}2. 定时器
javascript
// setTimeout(延迟执行)
setTimeout(() => {
console.log("延迟执行");
}, 1000);
// setInterval(定时执行)
const timer = setInterval(() => {
console.log("定时执行");
}, 1000);
// 清除定时器
clearInterval(timer);
// Promise 封装定时器
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function example() {
console.log("开始");
await delay(1000);
console.log("1秒后");
}3. 事件监听
javascript
// 监听按钮点击
button.addEventListener("click", () => {
console.log("点击了按钮");
});
// 等待事件(封装为 Promise)
function waitForClick(button) {
return new Promise(resolve => {
button.addEventListener("click", resolve);
});
}
async function handleClick() {
await waitForClick(button);
console.log("按钮被点击");
}六、最佳实践
1. 避免回调地狱
javascript
// 不好(回调地狱)
doSomething(result1 => {
doSomethingElse(result2 => {
doThirdThing(result3 => {
doFinalThing(result4);
});
});
});
// 好(Promise 链式调用)
doSomething()
.then(result1 => doSomethingElse(result1))
.then(result2 => doThirdThing(result2))
.then(result3 => doFinalThing(result3))
.catch(error => console.error(error));
// 更好(async/await)
async function doAll() {
try {
const result1 = await doSomething();
const result2 = await doSomethingElse(result1);
const result3 = await doThirdThing(result2);
await doFinalThing(result3);
} catch (error) {
console.error(error);
}
}2. 错误处理
javascript
// 每个 Promise 都应该有错误处理
async function handleData() {
try {
const data = await getData(); // 可能出错
const processed = await processData(data); // 可能出错
return processed;
} catch (error) {
// 统一错误处理
console.error("操作失败:", error);
throw error; // 继续抛出或处理
}
}3. 并行请求优化
javascript
// 不好(串行,速度慢)
async function fetchData() {
const user = await fetchUser();
const posts = await fetchPosts();
const comments = await fetchComments();
}
// 好(并行,速度快)
async function fetchData() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
}4. 超时控制
javascript
// 设置超时
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
return await response.json();
} catch (error) {
if (error.name === "AbortError") {
throw new Error("请求超时");
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}七、总结
- JavaScript 是单线程的,异步编程通过事件循环实现
- 回调函数容易产生回调地狱
- Promise 解决了回调地狱问题
- async/await 让异步代码看起来像同步代码
- 合理使用并行处理可以提升性能