JavaScript 中的异步(Asynchronous)是处理耗时操作(如网络请求、文件读写、定时器等)的核心机制,它允许程序在等待这些操作完成时继续执行其他任务,而非阻塞主线程。这对于保证页面流畅性(避免 UI 卡顿)至关重要。
一、为什么需要异步?
JavaScript 是单线程语言(同一时间只能执行一段代码),如果直接执行耗时操作(如等待服务器响应),会导致主线程阻塞,页面出现“假死”(无法点击、滚动等)。
异步机制通过“先注册任务,后续回调执行”的方式,解决了单线程的阻塞问题。
二、异步的三种实现方式
1. 回调函数(Callback)
最早的异步实现方式,通过将后续操作定义为函数,作为参数传递给异步任务,任务完成后自动调用。
示例:定时器回调
console.log('开始');
// 异步任务:1秒后执行回调函数
setTimeout(function callback() {
console.log('定时器完成');
}, 1000);
console.log('结束'); // 先执行,不会等待定时器
输出顺序:
开始
结束
定时器完成 // 1秒后执行
问题:多层嵌套时会产生“回调地狱”(Callback Hell),代码可读性差。
// 回调地狱示例(多层嵌套)
fetchData1(function(data1) {
fetchData2(data1, function(data2) {
fetchData3(data2, function(data3) {
// 更多嵌套...
});
});
});
2. Promise(ES6 新增)
解决回调地狱问题,通过链式调用(then())组织异步代码,使流程更清晰。
Promise 三种状态:
pending(进行中):初始状态。fulfilled(已成功):操作完成,调用resolve()触发。rejected(已失败):操作出错,调用reject()触发。
示例:用 Promise 处理异步请求
// 定义一个返回 Promise 的异步函数
function fetchData() {
return new Promise((resolve, reject) => {
// 模拟网络请求(1秒后返回数据)
setTimeout(() => {
const success = true;
if (success) {
resolve({
name: '鸿蒙', version: '4.0' }); // 成功时返回数据
} else {
reject(new Error('请求失败')); // 失败时返回错误
}
}, 1000);
});
}
// 调用异步函数(链式调用)
fetchData()
.then(data => {
console.log('数据:', data);
return data.version; // 可将结果传递给下一个 then
})
.then(version => {
console.log('版本号:', version);
})
.catch(error => {
console.error('出错:', error.message); // 捕获所有错误
})
.finally(() => {
console.log('无论成功失败都会执行');
});
优势:扁平化链式调用,避免嵌套;catch() 统一处理错误。
3. async/await(ES7 新增)
基于 Promise 的语法糖,用同步代码的形式编写异步逻辑,可读性更强。
使用规则:
async修饰函数:函数返回值自动包装为 Promise。await修饰 Promise:暂停执行,等待 Promise 完成后再继续。await只能在async函数中使用。
示例:用 async/await 重写上面的代码
// 复用上面定义的 fetchData()(返回 Promise)
async function handleData() {
try {
console.log('开始请求');
const data = await fetchData(); // 等待 Promise 完成
console.log('数据:', data);
const version = data.version;
console.log('版本号:', version);
} catch (error) {
console.error('出错:', error.message); // 捕获错误
} finally {
console.log('处理结束');
}
}
// 调用异步函数
handleData();
优势:代码结构与同步逻辑一致,最接近自然语言,易于理解和调试。
三、异步执行顺序:事件循环(Event Loop)
JavaScript 异步代码的执行依赖事件循环机制,核心流程:
- 同步任务:直接在主线程执行,执行完立即返回。
- 异步任务:
- 放入“任务队列”(分为宏任务队列和微任务队列)。
- 主线程空闲时,优先执行所有微任务,再执行一个宏任务,反复循环。
常见任务类型:
- 宏任务:
setTimeout、setInterval、DOM 事件、网络请求等。 - 微任务:
Promise.then/catch/finally、async/await、queueMicrotask等。
执行顺序示例:
console.log('1(同步)');
setTimeout(() => {
console.log('2(宏任务)');
}, 0);
Promise.resolve().then(() => {
console.log('3(微任务)');
});
console.log('4(同步)');
输出顺序:
1(同步)
4(同步)
3(微任务) // 同步任务执行完后,先清空调用微任务
2(宏任务) // 微任务执行完后,执行一个宏任务
四、实际开发中的异步场景
网络请求:用
fetch或axios(基于 Promise)获取后端数据。async function getUsers() { const response = await fetch('https://api.example.com/users'); const users = await response.json(); // 解析 JSON(也是异步) console.log(users); }文件操作:浏览器中读取本地文件(如
FileReader)。function readFileAsync(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); // 成功回调 reader.onerror = () => reject(reader.error); // 失败回调 reader.readAsText(file); // 异步读取文件 }); }定时器:延迟执行或定期执行任务(如轮询接口)。
// 轮询数据(每3秒一次) async function pollData() { while (true) { await fetchData(); // 等待本次请求完成 await new Promise(resolve => setTimeout(resolve, 3000)); // 等待3秒 } }
总结
- 异步核心:避免单线程阻塞,提高程序响应速度。
- 实现方式:从回调函数→Promise→async/await,逐步优化可读性和易用性,推荐优先使用
async/await。 - 关键机制:事件循环决定异步任务的执行顺序,记住“先微任务,后宏任务”。
掌握异步是 JavaScript 进阶的关键,尤其在处理网络请求、定时器等场景时不可或缺。