Appearance
JavaScript 异步编程
异步编程是JavaScript的核心特性之一,允许程序在等待长时间操作(如网络请求、文件读取)时继续执行其他任务。理解异步编程对于开发现代Web应用至关重要。
异步编程基础
同步 vs 异步
同步代码:代码按顺序执行,每一步都必须等待前一步完成。
javascript
console.log('第一步');
console.log('第二步');
console.log('第三步');
// 输出顺序:第一步 -> 第二步 -> 第三步
异步代码:代码可以启动一个操作后立即继续执行,操作完成后再处理结果。
javascript
console.log('第一步');
setTimeout(() => console.log('异步操作'), 0);
console.log('第二步');
// 输出顺序:第一步 -> 第二步 -> 异步操作
JavaScript 单线程与事件循环
JavaScript运行在单线程上,使用事件循环处理异步操作:
javascript
// 调用栈(Call Stack)、回调队列(Callback Queue)和事件循环(Event Loop)协同工作
console.log('开始');
setTimeout(() => {
console.log('setTimeout 1');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 1');
});
setTimeout(() => {
console.log('setTimeout 2');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 2');
});
console.log('结束');
// 输出顺序:
// 开始
// 结束
// Promise 1
// Promise 2
// setTimeout 1
// setTimeout 2
回调函数
回调函数是最早的异步处理方式,但也容易导致"回调地狱":
基本回调
javascript
// 简单的异步操作
function fetchData(callback) {
setTimeout(() => {
const data = '模拟数据';
callback(null, data); // 第一个参数是错误,第二个是数据
}, 1000);
}
fetchData((error, data) => {
if (error) {
console.error('发生错误:', error);
} else {
console.log('获取到数据:', data);
}
});
回调地狱(Callback Hell)
javascript
// 串行执行多个异步操作
getData(function(a) {
getMoreData(a, function(b) {
getEvenMoreData(b, function(c) {
getEvenEvenMoreData(c, function(d) {
// 嵌套过深,难以维护
console.log('最终数据:', d);
});
});
});
});
解决回调地狱
javascript
// 使用命名函数
function step1Callback(err, result1) {
if (err) return console.error(err);
step2(result1, step2Callback);
}
function step2Callback(err, result2) {
if (err) return console.error(err);
step3(result2, step3Callback);
}
function step3Callback(err, result3) {
if (err) return console.error(err);
console.log('最终结果:', result3);
}
step1(step1Callback);
Promise
Promise是ES6引入的异步编程解决方案,用于更好地处理异步操作:
Promise 基础
javascript
// Promise有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5; // 模拟随机成功/失败
if (success) {
resolve('数据获取成功');
} else {
reject(new Error('数据获取失败'));
}
}, 1000);
});
}
// 使用Promise
fetchData()
.then(data => {
console.log(data);
return data.toUpperCase(); // 可以链式调用
})
.then(upperData => {
console.log('处理后的数据:', upperData);
})
.catch(error => {
console.error('错误:', error.message);
});
Promise 静态方法
javascript
// Promise.resolve() - 创建已解决的Promise
const resolvedPromise = Promise.resolve('已解决的Promise');
// Promise.reject() - 创建已拒绝的Promise
const rejectedPromise = Promise.reject(new Error('已拒绝的Promise'));
// Promise.all() - 所有Promise都成功才成功,任何一个失败则整体失败
const promises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
];
Promise.all(promises)
.then(results => console.log('所有Promise结果:', results)) // [1, 2, 3]
.catch(error => console.error('有Promise失败:', error));
// Promise.allSettled() - 等待所有Promise完成,无论成功或失败
Promise.allSettled([
Promise.resolve('成功'),
Promise.reject('失败'),
Promise.resolve('成功')
])
.then(results => {
console.log('所有Promise完成:', results);
// 结果: [{status: 'fulfilled', value: '成功'}, {status: 'rejected', reason: '失败'}, {status: 'fulfilled', value: '成功'}]
});
// Promise.race() - 第一个完成的Promise决定结果
Promise.race([
new Promise(resolve => setTimeout(() => resolve('慢'), 2000)),
new Promise(resolve => setTimeout(() => resolve('快'), 1000)),
new Promise((_, reject) => setTimeout(() => reject('更快'), 500))
])
.then(result => console.log('最快的结果:', result)) // '更快'(因为reject更快)
.catch(error => console.error('最快的错误:', error));
Promise 链式调用
javascript
function fetchUser(id) {
return new Promise(resolve => {
setTimeout(() => resolve({ id, name: `用户${id}` }), 500);
});
}
function fetchUserPosts(userId) {
return new Promise(resolve => {
setTimeout(() => resolve([`帖子1-${userId}`, `帖子2-${userId}`]), 500);
});
}
// 链式调用
fetchUser(123)
.then(user => {
console.log('获取用户:', user);
return fetchUserPosts(user.id); // 返回Promise
})
.then(posts => {
console.log('获取帖子:', posts);
return posts.length; // 返回普通值
})
.then(postCount => {
console.log('帖子数量:', postCount);
})
.catch(error => {
console.error('操作失败:', error);
});
async/await
ES2017引入的async/await语法使异步代码看起来像同步代码:
基本用法
javascript
// 使用async关键字标记函数
async function getData() {
try {
// 使用await等待Promise解决
const data = await fetchData();
console.log('获取到数据:', data);
return data;
} catch (error) {
console.error('错误:', error.message);
throw error; // 重新抛出错误
}
}
// 调用async函数
getData().then(result => {
console.log('函数执行完成,结果:', result);
});
并行执行
javascript
async function fetchMultipleData() {
try {
// 并行执行(同时发起请求)
const [users, posts, comments] = await Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
]);
const [usersData, postsData, commentsData] = await Promise.all([
users.json(),
posts.json(),
comments.json()
]);
return { users: usersData, posts: postsData, comments: commentsData };
} catch (error) {
console.error('获取数据失败:', error);
}
}
// 或者使用Promise.allSettled来处理可能部分失败的情况
async function fetchMultipleDataSafe() {
const results = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
const data = {};
results.forEach((result, index) => {
const keys = ['users', 'posts', 'comments'];
if (result.status === 'fulfilled') {
data[keys[index]] = result.value;
} else {
console.error(`${keys[index]} 获取失败:`, result.reason);
data[keys[index]] = null;
}
});
return data;
}
错误处理
javascript
// 全局错误处理
async function handleRequest(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
if (error instanceof TypeError) {
console.error('网络错误:', error.message);
} else if (error.message.includes('HTTP错误')) {
console.error('服务器错误:', error.message);
} else {
console.error('未知错误:', error.message);
}
throw error; // 重新抛出,让调用者处理
}
}
// 通用错误处理函数
async function safeAsync(asyncFn) {
try {
const result = await asyncFn();
return [null, result];
} catch (error) {
return [error, null];
}
}
// 使用示例
async function example() {
const [error, data] = await safeAsync(() => fetch('/api/data'));
if (error) {
console.error('请求失败:', error);
} else {
console.log('数据:', data);
}
}
Fetch API
现代浏览器中的Fetch API基于Promise,用于发起网络请求:
基本用法
javascript
// GET 请求
async function fetchUsers() {
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const users = await response.json();
return users;
} catch (error) {
console.error('获取用户失败:', error);
throw error;
}
}
// POST 请求
async function createUser(userData) {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const newUser = await response.json();
return newUser;
} catch (error) {
console.error('创建用户失败:', error);
throw error;
}
}
请求配置
javascript
// 复杂请求示例
async function apiRequest(url, options = {}) {
const defaultOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // 包含cookies
};
const config = { ...defaultOptions, ...options };
// 添加认证头
const token = localStorage.getItem('authToken');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
try {
const response = await fetch(url, config);
// 检查响应状态
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP ${response.status}`);
}
// 根据内容类型解析响应
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else {
return await response.text();
}
} catch (error) {
if (error.name === 'TypeError') {
throw new Error('网络连接失败');
}
throw error;
}
}
生成器与异步编程
Generator函数也可以用于异步编程:
javascript
// 简单的Generator Runner
function run(generator) {
const iterator = generator();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value).then(res => {
return handle(iterator.next(res));
}).catch(err => {
return handle(iterator.throw(err));
});
}
try {
return handle(iterator.next());
} catch (error) {
return Promise.reject(error);
}
}
// 使用Generator处理异步
function* asyncOperation() {
try {
const user = yield fetch('/api/user').then(r => r.json());
const posts = yield fetch(`/api/posts?userId=${user.id}`).then(r => r.json());
return { user, posts };
} catch (error) {
console.error('Generator错误:', error);
}
}
// 执行
run(asyncOperation).then(result => {
console.log('结果:', result);
});
异步迭代器
ES2018引入了异步迭代器:
javascript
// 异步可迭代对象
class AsyncQueue {
constructor() {
this.items = [];
this.waiting = [];
}
push(item) {
if (this.waiting.length > 0) {
const resolver = this.waiting.shift();
resolver({ done: false, value: item });
} else {
this.items.push(item);
}
}
async *[Symbol.asyncIterator]() {
while (true) {
if (this.items.length > 0) {
yield this.items.shift();
} else {
await new Promise(resolve => {
this.waiting.push(resolve);
});
}
}
}
}
// 使用异步迭代器
async function processQueue() {
const queue = new AsyncQueue();
// 模拟添加项目
setTimeout(() => queue.push('项目1'), 1000);
setTimeout(() => queue.push('项目2'), 2000);
setTimeout(() => queue.push('项目3'), 3000);
// 异步迭代
for await (const item of queue) {
console.log('处理:', item);
}
}
实际应用示例
API 服务封装
javascript
class ApiService {
constructor(baseURL, defaultHeaders = {}) {
this.baseURL = baseURL;
this.defaultHeaders = {
'Content-Type': 'application/json',
...defaultHeaders
};
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
headers: { ...this.defaultHeaders, ...options.headers },
...options
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`请求失败: ${response.status} ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
} catch (error) {
console.error(`API请求错误 [${options.method || 'GET'}] ${url}:`, error);
throw error;
}
}
get(endpoint, params = {}) {
const queryString = new URLSearchParams(params).toString();
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
return this.request(url, { method: 'GET' });
}
post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
}
put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
}
// 使用API服务
const api = new ApiService('https://api.example.com');
async function example() {
try {
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John', email: 'john@example.com' });
console.log('用户列表:', users);
console.log('新用户:', newUser);
} catch (error) {
console.error('API操作失败:', error);
}
}
并发控制
javascript
// 限制并发数量的异步任务执行器
class ConcurrencyController {
constructor(limit = 5) {
this.limit = limit;
this.running = 0;
this.queue = [];
}
async add(asyncFn) {
return new Promise((resolve, reject) => {
this.queue.push({
asyncFn,
resolve,
reject
});
this.process();
});
}
async process() {
if (this.running >= this.limit || this.queue.length === 0) {
return;
}
this.running++;
const { asyncFn, resolve, reject } = this.queue.shift();
try {
const result = await asyncFn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process(); // 继续处理队列
}
}
}
// 使用并发控制器
const controller = new ConcurrencyController(3); // 最多同时执行3个任务
async function fetchWithLimit(urls) {
const promises = urls.map(url =>
controller.add(() => fetch(url).then(r => r.json()))
);
return Promise.all(promises);
}
异步编程是JavaScript开发的核心技能,掌握Promise、async/await等现代异步编程模式对于构建高性能的Web应用至关重要。