事件循环
欢迎来到我的博客!
使用 Markdown 格式编写。
今天学到了什么

异步任务(Asynchronous Task)详解
异步任务是指在 JavaScript 中不会立即执行完成,需要等待一段时间才能返回结果的操作。与同步任务不同,异步任务不会阻塞主线程,而是在后台处理,完成后通过回调函数或 Promise 通知主线程。
1. 异步任务的定义
-
同步任务:立即执行的任务,会阻塞主线程直到完成。例如:
const sum = 1 + 2; // 同步任务,立即执行console.log(sum); // 同步任务,立即执行 -
异步任务:不会立即完成的任务,执行时会释放主线程,完成后再通知主线程。例如:
setTimeout(() => console.log('延迟执行'), 1000); // 异步任务,1秒后执行fetch('/api/data') // 异步任务,等待网络响应.then(res => res.json());
2. 常见的异步任务类型
| 类型 | 示例 | 特点 |
|---|---|---|
| 网络请求 | fetch()、axios.get() | 等待服务器响应 |
| 定时器 | setTimeout()、setInterval() | 延迟执行 |
| 文件操作 | fs.readFile()(Node.js) | 读写文件系统 |
| DOM 事件 | element.addEventListener('click', callback) | 等待用户交互 |
| Promise | new Promise((resolve) => setTimeout(resolve, 1000)) | 自定义异步操作 |
| async/await | await fetch('/api/data') | 基于 Promise 的语法糖 |
3. 异步任务的执行机制
JavaScript 通过事件循环(Event Loop) 和任务队列(Task Queue) 来处理异步任务:
- 执行栈:主线程执行同步任务,形成执行栈。
- 任务队列:异步任务完成后,将回调函数放入任务队列。
- 事件循环:执行栈为空时,从任务队列中取出任务执行。
执行流程示例:

console.log('1'); // 同步任务,立即执行
setTimeout(() => console.log('2'), 0); // 异步任务,放入任务队列
console.log('3'); // 同步任务,立即执行
// 执行结果:1 → 3 → 24. 异步任务的分类
在现代 JavaScript 中,异步任务分为两类:
-
宏任务(Macro Task):
- 包括:
setTimeout、setInterval、I/O操作、DOM 事件、setImmediate(Node.js) - 执行顺序:每个宏任务执行完后,会检查并执行所有微任务
- 包括:
-
微任务(Micro Task):
- 包括:
Promise.then()、async/await、process.nextTick(Node.js)、queueMicrotask - 执行顺序:当前宏任务执行完后,立即执行所有微任务
- 包括:
5. 为什么需要异步任务?
- 避免阻塞主线程:如果所有操作都是同步的,网络请求或文件读写等耗时操作会导致页面卡顿,用户体验差。
- 提高性能:主线程可以在等待异步任务完成时处理其他工作,充分利用系统资源。
- 处理并发操作:虽然 JavaScript 是单线程的,但通过异步任务可以模拟并发效果,同时处理多个操作。
6. 异步任务的处理方式
-
回调函数:早期的异步处理方式,容易导致回调地狱(Callback Hell)。
fs.readFile('file1.txt', (err, data1) => {fs.readFile('file2.txt', (err, data2) => {fs.readFile('file3.txt', (err, data3) => {// 嵌套多层,代码难以维护});});}); -
Promise:解决回调地狱,提供链式调用。
readFile('file1.txt').then(data1 => readFile('file2.txt')).then(data2 => readFile('file3.txt')).then(data3 => console.log(data3)).catch(err => console.error(err)); -
async/await:Promise 的语法糖,使代码更像同步代码。
async function readFiles() {try {const data1 = await readFile('file1.txt');const data2 = await readFile('file2.txt');const data3 = await readFile('file3.txt');console.log(data3);} catch (err) {console.error(err);}}
总结:异步任务是 JavaScript 中处理耗时操作的核心机制,通过事件循环和任务队列实现非阻塞执行,提高了代码的执行效率和用户体验。现代 JavaScript 提供了 Promise 和 async/await 等语法糖,使异步代码的编写和阅读更加简洁直观。
流程图
浏览器是多线程的,由浏览器来执行异步任务
执行栈与异步任务结果的处理
当执行栈中的任务需要使用异步任务的结果时,确实需要特殊处理,否则会出现获取不到结果或报错的情况。但 JavaScript 通过回调函数、Promise 和 async/await 等机制,确保了异步任务的结果能够被正确处理,同时不会阻塞执行栈。
1. 错误的处理方式:直接使用异步任务的结果
如果直接在执行栈中尝试使用异步任务的结果,会导致获取到 undefined 或报错,因为异步任务还未完成:
// 错误示例function getAsyncData() { let result; setTimeout(() => { result = '异步数据'; }, 1000); return result; // 此时 result 还是 undefined}
const data = getAsyncData();console.log(data); // 输出 undefined,而不是 '异步数据'原因:setTimeout 是异步任务,会被放入任务队列,而 return result 是同步任务,会立即执行,此时 result 还未被赋值。
2. 正确的处理方式
JavaScript 提供了多种方式来处理异步任务的结果,确保在结果可用时再使用:
方式一:回调函数
function getAsyncData(callback) { setTimeout(() => { const result = '异步数据'; callback(result); // 异步任务完成后调用回调函数 }, 1000);}
getAsyncData((data) => { console.log(data); // 1秒后输出 '异步数据'});方式二:Promise
function getAsyncData() { return new Promise((resolve) => { setTimeout(() => { resolve('异步数据'); }, 1000); });}
getAsyncData().then((data) => { console.log(data); // 1秒后输出 '异步数据'});方式三:async/await
function getAsyncData() { return new Promise((resolve) => { setTimeout(() => { resolve('异步数据'); }, 1000); });}
async function main() { const data = await getAsyncData(); // 暂停执行,等待异步任务完成 console.log(data); // 1秒后输出 '异步数据'}
main();3. 为什么不会阻塞?
JavaScript 的异步机制确保了即使需要等待异步任务的结果,也不会阻塞执行栈:
- 事件循环:异步任务会被放入任务队列,执行栈在遇到异步任务时会继续执行其他同步任务,不会等待。
- 回调机制:异步任务完成后,通过回调函数通知执行栈,此时执行栈已经处理完了其他任务。
- await 暂停:使用
await时,虽然函数会暂停执行,但会释放主线程,允许其他代码执行。
4. 实际应用中的最佳实践
- 使用 Promise 或 async/await:现代 JavaScript 推荐使用这些方式处理异步任务,代码更清晰。
- 避免回调地狱:使用 Promise 链式调用或 async/await 避免多层嵌套的回调。
- 错误处理:使用
try/catch(async/await)或.catch()(Promise)捕获异步操作中的错误。 - 并行处理:对于多个独立的异步任务,使用
Promise.all()并行执行,提高效率。
总结:当执行栈中的任务需要使用异步任务的结果时,不能直接获取,需要通过回调函数、Promise 或 async/await 等机制来处理。这些机制确保了异步任务的结果能够被正确获取,同时不会阻塞执行栈,保证了 JavaScript 的单线程执行效率。
例子1
你的理解完全正确!
axios.get('/api/list') 确实是一个异步任务,而 console.log(list) 是同步任务,会立即执行。让我详细解释这个执行过程:
执行流程分析:
-
同步代码执行:
- 当代码执行到第9行时,
axios.get('/api/list')被调用,发起网络请求。 - 由于这是一个异步操作,JavaScript 引擎会将其放入任务队列(具体来说是宏任务队列)。
- 同时,
axios.get()会立即返回一个 Promise 对象,此时 Promise 处于pending状态。
- 当代码执行到第9行时,
-
链式调用:
- 接着调用
.then()方法,注册一个回调函数。这个回调函数会在 Promise 状态变为fulfilled时执行。 - 整个
axios.get('/api/list').then(...)表达式的结果仍然是一个 Promise 对象(因为.then()方法返回一个新的 Promise)。
- 接着调用
-
变量赋值:
- 将这个 Promise 对象赋值给
list变量。
- 将这个 Promise 对象赋值给
-
同步执行:
- 执行第13行的
console.log(list);,这是一个同步任务,会立即执行。 - 此时网络请求还未完成,Promise 仍处于
pending状态,所以会打印出Promise {<pending>}。
- 执行第13行的
-
异步回调执行:
- 当网络请求完成后,axios 会将 Promise 状态改为
fulfilled。 - 此时,
.then()中注册的回调函数会被放入微任务队列。 - 当执行栈为空时,事件循环会先执行所有微任务,所以回调函数会被执行,打印出 ‘请求完成’ 并返回
res.data。
- 当网络请求完成后,axios 会将 Promise 状态改为
执行顺序总结:
- 发起网络请求(异步任务,放入宏任务队列)
- 返回 Promise 对象(同步)
- 注册回调函数(同步)
- 赋值给
list变量(同步) - 打印
list(同步,输出 Promise 对象) - 网络请求完成(异步任务完成)
- 回调函数放入微任务队列
- 执行栈为空,执行微任务队列中的回调函数
- 打印 ‘请求完成’(异步回调)
为什么不是 undefined?
- 因为
axios.get()会立即返回一个 Promise 对象,而不是等到请求完成后才返回。 - 所以
list变量被赋值的是 Promise 对象,而不是 undefined。 - 只有当你尝试直接获取异步操作的结果而不使用回调或 Promise 时,才会得到 undefined。
验证方法:
你可以在浏览器控制台或 Node.js 环境中运行这段代码,观察输出结果,会看到类似这样的顺序:
Promise {<pending>}调用前开始执行调用后请求完成 // 来自 axios.get 的回调请求完成 // 来自 fetchData 函数的回调结论:你的理解是完全正确的!axios.get 是异步任务,会被放入任务队列,而 console.log(list) 是同步任务,会立即执行,打印出当时的 Promise 对象。
回调函数类型
-
同步回调:在当前执行栈中立即执行的回调函数。
function syncCallback(callback) {console.log('开始执行');callback(); // 立即执行回调console.log('执行完成');}syncCallback(() => console.log('回调执行'));// 输出:开始执行 → 回调执行 → 执行完成 -
异步回调:在任务队列中等待执行的回调函数。
function asyncCallback(callback) {console.log('开始执行');setTimeout(callback, 1000); // 1秒后执行回调console.log('执行完成');}asyncCallback(() => console.log('回调执行'));// 输出:开始执行 → 执行完成 → (1秒后)回调执行
async 和 await 详解
基本概念
async函数:- 用于定义一个异步函数,函数执行时会返回一个
Promise对象。 - 即使函数内部没有显式返回值,也会自动包装为
Promise.resolve(undefined)。
- 用于定义一个异步函数,函数执行时会返回一个
await关键字:- 只能在
async函数内部使用,用于等待一个Promise完成(resolve 或 reject)。 - 当遇到
await时,函数会暂停执行,直到Promise状态改变,然后继续执行并返回Promise的结果。
- 只能在
工作原理
// 定义一个 async 函数const getList = async () => { // await 等待 Promise 完成 const res = await axios.get('/api/list') return res.data}
// 调用 async 函数getList() .then(data => console.log(data)) // 处理成功结果 .catch(error => console.error(error)) // 处理错误-
执行流程
:
- 调用
getList()时,函数开始执行。 - 遇到
await axios.get('/api/list'),函数暂停执行,等待 axios 请求完成。 - axios 请求成功后,
await会返回响应结果,赋值给res。 - 函数继续执行,返回
res.data,并将其包装为Promise.resolve(res.data)。
- 调用
优势
- 代码可读性高:避免了 Promise 链式调用(
.then())的嵌套,代码结构更清晰。 - 错误处理方便:可以使用
try/catch捕获异步操作中的错误,与同步代码的错误处理方式一致。 - 逻辑更直观:按照同步代码的逻辑顺序编写异步操作,符合人的思维习惯。
错误处理
const getList = async () => { try { const res = await axios.get('/api/list') return res.data } catch (error) { console.error('请求失败:', error) // 可以返回默认值或重新抛出错误 return [] }}与 Promise 的关系
async函数是 Promise 的语法糖,内部仍使用 Promise 实现。await只能等待 Promise 对象,如果等待的不是 Promise,会自动包装为Promise.resolve(值)。async函数的返回值始终是 Promise,即使内部没有await。
实际应用场景
- API 调用:如示例中的
axios.get('/api/list'),等待接口返回数据。 - 文件操作:如读取文件、写入文件等异步操作。
- 定时器:结合
Promise和setTimeout实现延迟操作。 - 多步异步操作:需要按顺序执行多个异步任务时,代码更清晰。
注意事项
await只能在async函数内部使用,否则会报错。- 多个
await会串行执行,如需并行执行多个异步操作,应使用Promise.all()。 async函数内部的错误需要用try/catch捕获,否则会导致返回的 Promise 被拒绝。
总结:async/await 是处理异步操作的现代方式,通过简化代码结构和错误处理,让异步代码更易于理解和维护,是当前前端开发中处理异步操作的主流方案。
Async/Await 的工作流程
async function fetchData() { console.log('开始执行'); const res = await fetch('/api/list'); // 暂停执行 console.log('请求完成'); return res.json();}
console.log('调用前');fetchData();console.log('调用后');执行顺序:
- 打印
调用前 - 执行
fetchData(),打印开始执行 - 遇到
await fetch('/api/list'),将 fetch 操作放入后台处理,函数暂停执行,释放主线程 - 主线程继续执行,打印
调用后 - fetch 操作完成后,将回调函数放入任务队列
- 事件循环检测到执行栈为空,从任务队列取出回调,恢复
fetchData()的执行 - 打印
请求完成,返回结果
赞助支持
如果这篇文章对你有帮助,欢迎赞助支持!
部分内容可能已过时