Appearance
异步编程
JavaScript 是单线程语言,异步编程是处理耗时操作的关键技术。本章将深入介绍回调函数、Promise、async/await 等异步编程方式。
为什么需要异步
JavaScript 单线程特性
javascript
// JavaScript 是单线程的,代码按顺序执行
console.log('开始');
console.log('中间');
console.log('结束');
// 输出:开始 -> 中间 -> 结束
// 同步操作会阻塞后续代码
function syncOperation() {
let start = Date.now();
while (Date.now() - start < 2000) {
// 模拟耗时操作,阻塞 2 秒
}
console.log('耗时操作完成');
}
console.log('开始');
syncOperation(); // 阻塞 2 秒
console.log('结束');
// 输出:开始 -> (等待 2 秒) -> 耗时操作完成 -> 结束
// 异步操作不会阻塞
console.log('开始');
setTimeout(() => {
console.log('异步操作完成');
}, 2000);
console.log('结束');
// 输出:开始 -> 结束 -> (2 秒后) -> 异步操作完成同步 vs 异步
javascript
// 同步代码:按顺序执行,等待完成
function syncAdd(a, b) {
return a + b;
}
let result = syncAdd(1, 2); // 立即得到结果
console.log(result); // 3
// 异步代码:不等待完成,通过回调获取结果
function asyncAdd(a, b, callback) {
setTimeout(() => {
callback(a + b);
}, 1000);
}
asyncAdd(1, 2, (result) => {
console.log(result); // 1 秒后输出 3
});
console.log('这行先执行'); // 先输出回调函数
基本概念
javascript
// 回调函数:作为参数传递给其他函数的函数
function greet(name, callback) {
console.log('你好,' + name);
callback();
}
greet('张三', function() {
console.log('回调函数执行');
});
// 数组方法中的回调
let numbers = [1, 2, 3, 4, 5];
// forEach
numbers.forEach(function(num, index) {
console.log(`索引 ${index}: ${num}`);
});
// map
let doubled = numbers.map(function(num) {
return num * 2;
});
// filter
let evens = numbers.filter(function(num) {
return num % 2 === 0;
});
// 箭头函数简化
numbers.forEach(num => console.log(num));
let tripled = numbers.map(num => num * 3);回调地狱
javascript
// 回调地狱:多层嵌套的回调函数
function getUser(userId, callback) {
setTimeout(() => {
callback({ id: userId, name: '张三' });
}, 1000);
}
function getOrders(user, callback) {
setTimeout(() => {
callback([
{ id: 1, product: '手机' },
{ id: 2, product: '电脑' }
]);
}, 1000);
}
function getOrderDetails(order, callback) {
setTimeout(() => {
callback({ ...order, price: 5999, quantity: 1 });
}, 1000);
}
// 回调地狱
getUser(1, function(user) {
console.log('用户:', user);
getOrders(user, function(orders) {
console.log('订单:', orders);
getOrderDetails(orders[0], function(details) {
console.log('订单详情:', details);
// 继续嵌套...
});
});
});
// 问题:
// 1. 代码难以阅读和维护
// 2. 错误处理复杂
// 3. 难以复用解决回调地狱
javascript
// 方法 1:拆分函数
function handleUser(user) {
console.log('用户:', user);
getOrders(user, handleOrders);
}
function handleOrders(orders) {
console.log('订单:', orders);
getOrderDetails(orders[0], handleDetails);
}
function handleDetails(details) {
console.log('订单详情:', details);
}
getUser(1, handleUser);
// 方法 2:使用 Promise(推荐)
getUser(1)
.then(user => {
console.log('用户:', user);
return getOrders(user);
})
.then(orders => {
console.log('订单:', orders);
return getOrderDetails(orders[0]);
})
.then(details => {
console.log('订单详情:', details);
})
.catch(error => {
console.log('错误:', error);
});
// 方法 3:使用 async/await(最推荐)
async function processOrder() {
try {
const user = await getUser(1);
console.log('用户:', user);
const orders = await getOrders(user);
console.log('订单:', orders);
const details = await getOrderDetails(orders[0]);
console.log('订单详情:', details);
} catch (error) {
console.log('错误:', error);
}
}
processOrder();Promise
创建 Promise
javascript
// Promise 构造函数
const promise = new Promise((resolve, reject) => {
// resolve:成功时调用
// reject:失败时调用
let success = true;
if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
});
// Promise 状态
// pending:进行中(初始状态)
// fulfilled:已成功
// rejected:已失败
// 状态只能改变一次
const p = new Promise((resolve, reject) => {
resolve('成功');
reject('失败'); // 无效,状态已经改变
});
// 封装异步操作为 Promise
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// 使用
delay(1000).then(() => {
console.log('1 秒后执行');
});
// 封装 AJAX 为 Promise
function ajax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
};
xhr.onerror = () => reject(new Error('网络错误'));
xhr.send();
});
}使用 Promise
javascript
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
let success = Math.random() > 0.5;
if (success) {
resolve('成功');
} else {
reject('失败');
}
}, 1000);
});
// then:处理成功
// catch:处理失败
// finally:无论成功失败都执行
promise
.then(result => {
console.log('成功:', result);
})
.catch(error => {
console.log('失败:', error);
})
.finally(() => {
console.log('完成');
});
// then 可以接受两个参数
promise.then(
result => console.log('成功:', result),
error => console.log('失败:', error)
);
// 链式调用
promise
.then(result => {
console.log('第一步:', result);
return '处理后的结果';
})
.then(result => {
console.log('第二步:', result);
return new Promise(resolve => {
setTimeout(() => resolve('异步结果'), 1000);
});
})
.then(result => {
console.log('第三步:', result);
});
// 错误冒泡
Promise.resolve()
.then(() => {
throw new Error('失败');
})
.then(() => {
console.log('不会执行');
})
.catch(error => {
console.log('捕获错误:', error.message);
});Promise 静态方法
javascript
// Promise.resolve():返回成功的 Promise
const p1 = Promise.resolve('成功');
p1.then(result => console.log(result)); // 成功
// Promise.reject():返回失败的 Promise
const p2 = Promise.reject('失败');
p2.catch(error => console.log(error)); // 失败
// Promise.all():所有都成功才成功
const p3 = Promise.resolve(1);
const p4 = Promise.resolve(2);
const p5 = Promise.resolve(3);
Promise.all([p3, p4, p5])
.then(results => {
console.log(results); // [1, 2, 3]
})
.catch(error => {
console.log('有一个失败:', error);
});
// 应用:同时请求多个接口
Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([users, posts, comments]) => {
console.log('所有数据加载完成');
});
// Promise.race():第一个完成的结果
Promise.race([
new Promise(resolve => setTimeout(() => resolve('慢'), 2000)),
new Promise(resolve => setTimeout(() => resolve('快'), 1000))
])
.then(result => console.log(result)); // 快
// 应用:请求超时
function fetchWithTimeout(url, timeout = 5000) {
return Promise.race([
fetch(url),
new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), timeout);
})
]);
}
// Promise.allSettled():等待所有完成(无论成功失败)
Promise.allSettled([
Promise.resolve('成功1'),
Promise.reject('失败'),
Promise.resolve('成功2')
])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
});
// Promise.any():任意一个成功就成功(ES2021)
Promise.any([
Promise.reject('失败1'),
Promise.resolve('成功'),
Promise.reject('失败2')
])
.then(result => console.log(result)); // 成功async/await
基本用法
javascript
// async 函数返回 Promise
async function greet() {
return '你好';
}
// 等价于
function greet() {
return Promise.resolve('你好');
}
// 调用 async 函数
greet().then(result => console.log(result)); // 你好
// await 等待 Promise 完成
async function getData() {
const response = await fetch('/api/data');
const data = await response.json();
return data;
}
// await 只能在 async 函数内使用
// function wrong() {
// await fetch('/api/data'); // 错误
// }
// 顶层 await(ES2022,模块中可用)
// const data = await fetch('/api/data');错误处理
javascript
// try...catch 处理错误
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.log('错误:', error.message);
throw error; // 可以继续抛出
}
}
// 使用 catch 方法
fetchData().catch(error => {
console.log('外部捕获:', error);
});
// 封装错误处理
async function tryCatch(promise) {
try {
const data = await promise;
return [null, data];
} catch (error) {
return [error, null];
}
}
// 使用
const [error, data] = await tryCatch(fetch('/api/data'));
if (error) {
console.log('错误:', error);
} else {
console.log('数据:', data);
}并行执行
javascript
// 串行执行(一个接一个)
async function serial() {
const user = await fetch('/api/user').then(r => r.json());
const posts = await fetch('/api/posts').then(r => r.json());
const comments = await fetch('/api/comments').then(r => r.json());
return { user, posts, comments };
}
// 并行执行(同时进行)
async function parallel() {
const [user, posts, comments] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { user, posts, comments };
}
// 使用 Promise.all
async function fetchAll() {
const urls = ['/api/user', '/api/posts', '/api/comments'];
const responses = await Promise.all(urls.map(url =>
fetch(url).then(r => r.json())
));
return responses;
}
// for...of 循环中使用 await(串行)
async function processItems(items) {
for (const item of items) {
await processItem(item);
}
}
// 并行处理数组
async function processItemsParallel(items) {
await Promise.all(items.map(item => processItem(item)));
}循环中的异步
javascript
// for...of 循环(串行)
async function processArray(array) {
for (const item of array) {
await processItem(item);
}
}
// forEach 不支持 await
async function wrong() {
[1, 2, 3].forEach(async (item) => {
await processItem(item); // 不会等待
});
console.log('完成'); // 先输出
}
// map 返回 Promise 数组
async function processWithMap(array) {
const promises = array.map(item => processItem(item));
await Promise.all(promises);
}
// for 循环
async function processWithFor(array) {
for (let i = 0; i < array.length; i++) {
await processItem(array[i]);
}
}
// for await...of(异步迭代)
async function* asyncGenerator() {
yield Promise.resolve(1);
yield Promise.resolve(2);
yield Promise.resolve(3);
}
async function processAsyncIterator() {
for await (const value of asyncGenerator()) {
console.log(value);
}
}事件循环
执行机制
javascript
// JavaScript 执行顺序
console.log('1. 同步代码');
setTimeout(() => {
console.log('2. 宏任务');
}, 0);
Promise.resolve().then(() => {
console.log('3. 微任务');
});
console.log('4. 同步代码');
// 输出顺序:
// 1. 同步代码
// 4. 同步代码
// 3. 微任务
// 2. 宏任务
// 事件循环流程:
// 1. 执行同步代码
// 2. 执行微任务队列中的所有任务
// 3. 执行一个宏任务
// 4. 重复步骤 2-3宏任务和微任务
javascript
// 宏任务(Macro Task)
// - setTimeout
// - setInterval
// - setImmediate(Node.js)
// - I/O
// - UI 渲染
// 微任务(Micro Task)
// - Promise.then/catch/finally
// - process.nextTick(Node.js)
// - MutationObserver
// - queueMicrotask
// 执行顺序示例
async function test() {
console.log('1. async 函数开始');
await Promise.resolve();
console.log('2. await 后面');
console.log('3. async 函数结束');
}
console.log('0. 同步开始');
test();
console.log('4. 同步结束');
Promise.resolve().then(() => {
console.log('5. Promise 微任务');
});
setTimeout(() => {
console.log('6. setTimeout 宏任务');
}, 0);
// 输出顺序:
// 0. 同步开始
// 1. async 函数开始
// 4. 同步结束
// 2. await 后面
// 3. async 函数结束
// 5. Promise 微任务
// 6. setTimeout 宏任务queueMicrotask
javascript
// queueMicrotask:添加微任务
queueMicrotask(() => {
console.log('微任务');
});
// 等价于
Promise.resolve().then(() => {
console.log('微任务');
});
// 使用场景:确保代码在当前任务完成后执行
function doSomething() {
// 执行一些操作
// 确保回调在当前任务完成后执行
queueMicrotask(() => {
console.log('当前任务完成后执行');
});
}实战案例
请求重试
javascript
// 带重试的请求
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.log(`第 ${i + 1} 次尝试失败:`, error.message);
if (i === retries - 1) {
throw error;
}
// 等待一段时间后重试
await new Promise(resolve =>
setTimeout(resolve, 1000 * Math.pow(2, i))
);
}
}
}
// 使用
fetchWithRetry('/api/data')
.then(data => console.log(data))
.catch(error => console.log('所有重试失败:', error));并发控制
javascript
// 限制并发数量
async function limitConcurrency(tasks, limit) {
const results = [];
const executing = new Set();
for (const task of tasks) {
const promise = Promise.resolve().then(() => task());
results.push(promise);
executing.add(promise);
const cleanup = () => executing.delete(promise);
promise.then(cleanup, cleanup);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
// 使用
const tasks = urls.map(url => () => fetch(url));
const results = await limitConcurrency(tasks, 3); // 最多 3 个并发
// 简化版本:并发池
class ConcurrencyPool {
constructor(limit) {
this.limit = limit;
this.running = 0;
this.queue = [];
}
async run(task) {
if (this.running >= this.limit) {
await new Promise(resolve => this.queue.push(resolve));
}
this.running++;
try {
return await task();
} finally {
this.running--;
const next = this.queue.shift();
if (next) next();
}
}
}
// 使用
const pool = new ConcurrencyPool(3);
const results = await Promise.all(
urls.map(url => pool.run(() => fetch(url)))
);异步缓存
javascript
// 异步缓存装饰器
function asyncCache(fn) {
const cache = new Map();
return async function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const promise = fn.apply(this, args);
cache.set(key, promise);
try {
return await promise;
} catch (error) {
cache.delete(key);
throw error;
}
};
}
// 使用
const fetchUser = asyncCache(async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
});
// 第一次调用会请求
const user1 = await fetchUser(1);
// 第二次调用直接返回缓存
const user2 = await fetchUser(1);异步队列
javascript
// 异步队列
class AsyncQueue {
constructor() {
this.queue = [];
this.processing = false;
}
async enqueue(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) {
return;
}
this.processing = true;
while (this.queue.length > 0) {
const { task, resolve, reject } = this.queue.shift();
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
}
}
this.processing = false;
}
}
// 使用
const queue = new AsyncQueue();
queue.enqueue(() => fetch('/api/1'));
queue.enqueue(() => fetch('/api/2'));
queue.enqueue(() => fetch('/api/3'));小结
本章学习了 JavaScript 异步编程的核心知识:
- 异步概念:单线程、同步 vs 异步
- 回调函数:基本用法、回调地狱
- Promise:创建、使用、静态方法、链式调用
- async/await:语法糖、错误处理、并行执行
- 事件循环:宏任务、微任务、执行顺序
- 实战案例:请求重试、并发控制、异步缓存
掌握异步编程是成为 JavaScript 开发者的必备技能,建议多练习实际项目中的异步场景。
