𝑻𝒆𝒏𝑪𝒍𝒂𝒘正在头脑风暴···
𝑻𝒆𝒏𝑲𝒊𝑺𝒆𝒀𝒂の𝑨𝒈𝒆𝒏𝒕助手
𝑻𝒆𝒏-𝒇𝒍𝒂𝒔𝒉

JavaScript 异步编程完全指南

JavaScript 的异步编程是现代前端开发的核心内容。随着 Web 应用变得越来越复杂,处理异步操作(如网络请求、定时器、文件操作等)成为了每个开发者必须掌握的技能。本文将从零开始,带你深入理解 JavaScript 异步编程的各个方面,从基础的回调函数到最现代化的 async/await 语法。

一、同步 vs 异步

1.1 什么是同步编程

同步编程是按照代码顺序依次执行的,每一行代码都必须等待上一行代码执行完成:

console.log('开始执行');

const result = sum(1, 2);

console.log('结果:', result);

// 假设 sum 函数需要 2 秒
function sum(a, b) {
setTimeout(() => {
return a + b;
}, 2000);
}

console.log('执行结束');

执行顺序

  1. 输出 “开始执行”
  2. 调用 sum 函数(但不会等待,直接执行下一行)
  3. 输出 “结果:”(但 result 还是 undefined)
  4. 输出 “执行结束”
  5. 2 秒后 sum 函数返回 3

1.2 什么是异步编程

异步编程允许代码在等待异步操作(如网络请求、定时器等)完成时继续执行,不会阻塞主线程:

console.log('开始执行');

setTimeout(() => {
console.log('延迟执行');
}, 1000);

console.log('执行结束');

执行顺序

  1. 输出 “开始执行”
  2. 设置定时器(1 秒后执行回调函数)
  3. 输出 “执行结束”
  4. 1 秒后输出 “延迟执行”

1.3 同步和异步的区别

特性同步异步
执行方式顺序执行并行执行
主线程阻塞会阻塞不阻塞
使用场景文件操作网络请求、定时器

二、回调函数

2.1 基础回调函数

回调函数是最基本的异步编程方式,将函数作为参数传递给另一个函数,在异步操作完成后调用:

// 回调函数示例
function fetchData(callback) {
// 模拟异步操作
setTimeout(() => {
const data = {
id: 1,
name: '张三',
age: 25,
email: 'zhangsan@example.com'
};
callback(data);
}, 1000);
}

// 调用 fetchData 并传入回调函数
fetchData((user) => {
console.log('用户数据:', user);
console.log('姓名:', user.name);
console.log('年龄:', user.age);
console.log('邮箱:', user.email);
});

2.2 回调地狱

当多个异步操作有依赖关系时,会产生回调嵌套,这就是所谓的”回调地狱”:

// 回调地狱示例
getUser((user) => {
console.log('用户:', user.name);

getUserPosts(user.id, (posts) => {
console.log('用户的文章:', posts);

getPostComments(posts[0].id, (comments) => {
console.log('文章的评论:', comments);

// 可以继续嵌套更多回调...
});
});
});

function getUser(callback) {
setTimeout(() => {
callback({ name: '张三' });
}, 1000);
}

function getUserPosts(userId, callback) {
setTimeout(() => {
callback([
{ id: 1, title: '文章1' },
{ id: 2, title: '文章2' }
]);
}, 1000);
}

function getPostComments(postId, callback) {
setTimeout(() => {
callback([
{ id: 1, content: '评论1' },
{ id: 2, content: '评论2' }
]);
}, 1000);
}

回调地狱的问题

  1. 代码难以阅读和维护
  2. 嵌套过深,容易出错
  3. 代码难以扩展
  4. 错误处理复杂

2.3 解决回调地狱的方法

1. 使用 Promise

// 使用 Promise 重构
getUser()
.then(user => {
console.log('用户:', user.name);
return getUserPosts(user.id);
})
.then(posts => {
console.log('用户的文章:', posts);
return getPostComments(posts[0].id);
})
.then(comments => {
console.log('文章的评论:', comments);
})
.catch(error => {
console.error('错误:', error);
});

2. 使用 async/await

// 使用 async/await 重构
async function getUserData() {
try {
const user = await getUser();
console.log('用户:', user.name);

const posts = await getUserPosts(user.id);
console.log('用户的文章:', posts);

const comments = await getPostComments(posts[0].id);
console.log('文章的评论:', comments);
} catch (error) {
console.error('错误:', error);
}
}

getUserData();

三、Promise

3.1 Promise 的概念

Promise 是异步编程的一种解决方案,它是一个对象,代表一个异步操作的最终完成或失败。Promise 有三种状态:

  • pending:进行中
  • fulfilled(成功)
  • rejected(失败)

Promise 状态一旦改变,就不会再改变。

3.2 创建 Promise

// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const success = Math.random() > 0.3; // 70% 成功

if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});

// 处理 Promise
promise
.then((data) => {
console.log('成功:', data);
})
.catch((error) => {
console.error('失败:', error);
})
.finally(() => {
console.log('操作完成');
});

3.3 Promise 的三种状态转换

// pending -> fulfilled
const fulfilled = new Promise((resolve) => {
setTimeout(() => {
resolve('操作成功');
}, 1000);
});

// pending -> rejected
const rejected = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('操作失败'));
}, 1000);
});

// 状态一旦改变,不能再变
let state = 'pending';
let result;

const promise = new Promise((resolve, reject) => {
if (state === 'pending') {
resolve('成功'); // 状态变为 fulfilled
}
state = 'fulfilled'; // 这个赋值没有影响,因为状态已经改变
result = '这行代码不会执行';
});

promise.then((data) => {
console.log('数据:', data); // 输出:数据:成功
console.log('结果:', result); // 输出:结果:undefined
});

3.4 Promise 链式调用

Promise 支持链式调用,每次 then 都返回一个新的 Promise:

Promise.resolve(1)
.then((value) => {
console.log('第一步:', value); // 输出:第一步:1
return value * 2; // 返回 2
})
.then((value) => {
console.log('第二步:', value); // 输出:第二步:2
return value + 3; // 返回 5
})
.then((value) => {
console.log('第三步:', value); // 输出:第三步:5
})
.catch((error) => {
console.error('错误:', error);
})
.finally(() => {
console.log('操作完成'); // 输出:操作完成
});

3.5 Promise.all 和 Promise.race

Promise.all

Promise.all 接收一个 Promise 数组,当所有 Promise 都成功时,才返回结果;如果有任何一个失败,立即返回失败结果:

const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
const p4 = Promise.reject('失败');

Promise.all([p1, p2, p3, p4])
.then((values) => {
console.log('所有结果:', values); // 不会执行
})
.catch((error) => {
console.error('错误:', error); // 输出:错误:失败
});

实际应用:并发请求多个 API

async function fetchAllData() {
try {
// 并发请求多个 API
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(res => res.json()),
fetch('/api/posts').then(res => res.json()),
fetch('/api/comments').then(res => res.json())
]);

console.log('用户:', users);
console.log('文章:', posts);
console.log('评论:', comments);
} catch (error) {
console.error('请求失败:', error);
}
}

Promise.race

Promise.race 接收一个 Promise 数组,返回最先完成(无论是成功还是失败)的那个 Promise 的结果:

const p1 = new Promise(resolve => setTimeout(() => resolve('p1'), 100));
const p2 = new Promise(resolve => setTimeout(() => resolve('p2'), 200));
const p3 = new Promise(resolve => setTimeout(() => resolve('p3'), 50));

Promise.race([p1, p2, p3])
.then((value) => {
console.log('最先完成的:', value); // 输出:最先完成的:p3
});

实际应用:请求超时处理

function fetchWithTimeout(url, timeout = 5000) {
return Promise.race([
fetch(url),
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('请求超时'));
}, timeout);
})
]);
}

fetchWithTimeout('/api/data', 3000)
.then(res => res.json())
.then(data => console.log('数据:', data))
.catch(error => console.error('错误:', error));

3.6 Promise.allSettled

Promise.allSettled 会等待所有 Promise 完成,无论成功或失败,返回一个数组:

const p1 = Promise.resolve(1);
const p2 = Promise.reject('错误');
const p3 = Promise.resolve(3);

Promise.allSettled([p1, p2, p3])
.then((results) => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index}: 成功`, result.value);
} else {
console.error(`Promise ${index}: 失败`, result.reason);
}
});
});

输出

Promise 0: 成功 1
Promise 1: 失败 错误
Promise 2: 成功 3

四、async 和 await

4.1 async 函数

async 函数是一个返回 Promise 对象的函数:

async function getData() {
return 'Hello World';
}

// 等价于
function getData() {
return Promise.resolve('Hello World');
}

// 调用 async 函数
getData()
.then((result) => {
console.log(result); // 输出:Hello World
});

4.2 await 关键字

await 只能在 async 函数中使用,它会暂停异步函数的执行,等待 Promise 完成,然后返回结果:

async function fetchUser() {
// 等待 Promise 完成
const response = await fetch('/api/user');
const user = await response.json();

return user;
}

fetchUser()
.then(user => console.log('用户:', user))
.catch(error => console.error('错误:', error));

4.3 实战案例

案例 1:用户登录流程

async function login(email, password) {
try {
// 1. 发送登录请求
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password })
});

if (!response.ok) {
throw new Error('登录失败');
}

const user = await response.json();

// 2. 保存 token
localStorage.setItem('token', user.token);

// 3. 获取用户信息
const userProfile = await fetchUserProfile(user.id);

return {
success: true,
user: userProfile
};
} catch (error) {
console.error('登录失败:', error);
return {
success: false,
error: error.message
};
}
}

async function fetchUserProfile(userId) {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}

// 使用
login('zhangsan@example.com', 'password')
.then(result => {
if (result.success) {
console.log('登录成功:', result.user);
} else {
console.error('登录失败:', result.error);
}
});

案例 2:并行请求

async function fetchHomePageData() {
try {
// 并行请求多个 API
const [bannerData, navData, articles, hotTopics] = await Promise.all([
fetch('/api/banner').then(res => res.json()),
fetch('/api/nav').then(res => res.json()),
fetch('/api/articles').then(res => res.json()),
fetch('/api/hot-topics').then(res => res.json())
]);

return {
banner: bannerData,
nav: navData,
articles: articles,
hotTopics: hotTopics
};
} catch (error) {
console.error('获取首页数据失败:', error);
return null;
}
}

// 使用
fetchHomePageData()
.then(data => {
if (data) {
console.log('Banner:', data.banner);
console.log('导航:', data.nav);
console.log('文章:', data.articles);
console.log('热门话题:', data.hotTopics);
}
});

案例 3:表单提交

async function submitForm(formData) {
try {
// 1. 验证表单
const validation = validateForm(formData);
if (!validation.valid) {
return {
success: false,
errors: validation.errors
};
}

// 2. 提交表单数据
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
});

if (!response.ok) {
throw new Error('提交失败');
}

const result = await response.json();

// 3. 上传文件
if (formData.file) {
await uploadFile(formData.file, result.fileId);
}

return {
success: true,
data: result
};
} catch (error) {
console.error('表单提交失败:', error);
return {
success: false,
error: error.message
};
}
}

function validateForm(formData) {
const errors = {};

if (!formData.username) {
errors.username = '用户名不能为空';
}

if (!formData.email) {
errors.email = '邮箱不能为空';
}

return {
valid: Object.keys(errors).length === 0,
errors
};
}

async function uploadFile(file, fileId) {
const formData = new FormData();
formData.append('file', file);
formData.append('fileId', fileId);

await fetch('/api/upload', {
method: 'POST',
body: formData
});
}

// 使用
const form = {
username: '张三',
email: 'zhangsan@example.com',
password: 'password123',
file: document.getElementById('fileInput').files[0]
};

submitForm(form)
.then(result => {
if (result.success) {
console.log('提交成功:', result.data);
} else {
console.error('提交失败:', result.errors);
}
});

案例 4:轮播图

async function startCarousel(images, interval = 2000) {
let currentIndex = 0;
const slides = document.querySelectorAll('.slide');

function showSlide(index) {
slides.forEach((slide, i) => {
slide.classList.remove('active');
slide.style.opacity = i === index ? '1' : '0';
slide.style.transition = 'opacity 0.5s';
});
}

function nextSlide() {
currentIndex = (currentIndex + 1) % images.length;
showSlide(currentIndex);
}

// 初始显示
showSlide(currentIndex);

// 自动切换
return new Promise((resolve) => {
const timer = setInterval(nextSlide, interval);

// 可以通过外部函数停止轮播
function stop() {
clearInterval(timer);
resolve();
}

return {
stop
};
});
}

// 使用
async function initCarousel() {
const images = await fetch('/api/carousel').then(res => res.json());

const stop = await startCarousel(images);

// 5秒后自动停止
setTimeout(() => {
stop();
}, 10000);
}

initCarousel();

五、错误处理策略

5.1 try-catch 块

使用 try-catch 捕获异步操作中的错误:

async function riskyOperation() {
try {
const result = await fetch('/api/risky');
return await result.json();
} catch (error) {
console.error('操作失败:', error);
throw error; // 可以重新抛出错误
}
}

5.2 Promise.catch

fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log('数据:', data);
})
.catch(error => {
console.error('获取数据失败:', error);
});

5.3 错误边界

async function safeAsyncOperation(operation) {
try {
return await operation();
} catch (error) {
console.error('操作失败:', error);
// 返回默认值或重试
return null;
}
}

async function getUser(userId) {
return safeAsyncOperation(async () => {
const response = await fetch(`/api/users/${userId}`);
return await response.json();
});
}

const user = await getUser(1);
if (user) {
console.log('用户:', user.name);
} else {
console.log('用户不存在');
}

六、性能优化

6.1 并发控制

async function fetchInBatches(urls, batchSize = 3) {
const results = [];

for (let i = 0; i < urls.length; i += batchSize) {
const batch = urls.slice(i, i + batchSize);

const batchResults = await Promise.all(
batch.map(url => fetch(url).then(res => res.json()))
);

results.push(...batchResults);
}

return results;
}

const urls = [
'https://api.example.com/1',
'https://api.example.com/2',
'https://api.example.com/3',
'https://api.example.com/4',
'https://api.example.com/5'
];

// 每次并发 2 个请求
fetchInBatches(urls, 2)
.then(results => {
console.log('所有请求完成:', results);
});

6.2 请求取消

class AbortController {
constructor() {
this.abortController = new AbortController();
}

get signal() {
return this.abortController.signal;
}

abort() {
this.abortController.abort();
}
}

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
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求超时');
} else {
throw error;
}
}
}

fetchWithTimeout('/api/data', 3000);

6.3 缓存策略

class Cache {
constructor(ttl = 60000) {
this.cache = new Map();
this.ttl = ttl;
}

set(key, value) {
const expiresAt = Date.now() + this.ttl;
this.cache.set(key, { value, expiresAt });
}

get(key) {
const cached = this.cache.get(key);

if (!cached) return null;

if (Date.now() > cached.expiresAt) {
this.cache.delete(key);
return null;
}

return cached.value;
}

clear() {
this.cache.clear();
}
}

const cache = new Cache(5000);

async function fetchWithCache(url) {
// 检查缓存
const cached = cache.get(url);
if (cached) {
console.log('使用缓存');
return cached;
}

// 发起请求
const response = await fetch(url);
const data = await response.json();

// 存入缓存
cache.set(url, data);

return data;
}

// 使用
fetchWithCache('https://api.example.com/data');

七、总结

JavaScript 异步编程的方式:

  1. 回调函数:基础但易导致回调地狱
  2. Promise:改进异步处理,支持链式调用
  3. async/await:最现代化的异步编程方式

最佳实践:

  • 使用 try-catch 处理错误
  • 使用 Promise.all 并发请求
  • 合理使用缓存
  • 控制请求频率(节流/防抖)
  • 使用并发控制避免过多并发请求

掌握异步编程是现代前端开发的关键,希望本文能帮助你更好地理解和应用 JavaScript 异步编程!