中级前端进阶方向 框架篇第二十五步) 异步编程
摘要:本文系统梳理了JavaScript异步编程的发展历程,从最初的回调函数到Promise,再到Async/Await。重点分析了三种方案的特点:回调函数易导致"回调地狱";Promise通过链式调用改善了可读性;Async/Await使异步代码具有同步风格。文章还介绍了事件循环、微/宏任务等核心概念,并提供了Promise.all等工具方法的使用示例。最佳实践推荐优先使用A
为什么需要异步编程?
JavaScript 是单线程的。这意味着它一次只能做一件事。如果所有操作都是同步的,那么当遇到一个耗时很长的操作(比如网络请求、读取大文件、定时器)时,线程就会被“阻塞”,浏览器就会卡住,用户无法进行任何操作,体验极差。
异步编程就是为了解决单线程语言带来的“阻塞”问题。 它将耗时的操作“挂起”,先执行后面的代码,等这些耗时操作完成了,再回过头来执行相应的逻辑。
前端异步编程的演进史
前端异步编程主要经历了以下几个阶段:
1. 回调函数 (Callback)
这是最古老、最基础的异步解决方案。
原理:将一个函数(回调函数)作为参数传递给另一个函数,这个函数会在异步操作完成后被调用。
示例:
// 模拟一个异步操作,比如读取文件
function readFile(callback) {
setTimeout(() => {
const data = '这是文件内容';
callback(null, data); // 第一个参数是错误(如果有),第二个是数据
}, 1000);
}
// 使用回调函数
readFile((err, data) => {
if (err) {
console.error('出错啦:', err);
return;
}
console.log('文件内容是:', data);
});
console.log('我先执行了,不会等待readFile完成');
缺点(回调地狱 Callback Hell):
当多个异步操作存在依赖关系时,代码会变得层层嵌套,难以阅读和维护。
doSomething((result1) => {
doSomethingElse(result1, (result2) => {
doThirdThing(result2, (result3) => {
console.log('最终结果:' + result3);
}, failureCallback);
}, failureCallback);
}, failureCallback);
2. Promise (ES6)
Promise 是一个对象,它代表了一个异步操作的最终完成(或失败) 及其结果值。它是对回调方案的一种革命性改进。
核心状态:
-
pending:初始状态,既不是成功,也不是失败。
-
fulfilled:操作成功完成。
-
rejected:操作失败。
优点:
-
链式调用 (Chaining):解决了回调地狱的问题。
-
错误冒泡:错误可以一直向后传递,直到被捕获。
示例:
function readFile() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // 模拟成功或失败
if (success) {
resolve('这是Promise的文件内容'); // 成功时调用
} else {
reject('读取文件失败'); // 失败时调用
}
}, 1000);
});
}
// 使用Promise
readFile()
.then((data) => {
console.log('文件内容是:', data);
return processData(data); // 返回一个新的Promise,继续链式调用
})
.then((processedData) => {
console.log('处理后的数据:', processedData);
})
.catch((err) => {
// 捕获前面任何一步发生的错误
console.error('出错啦:', err);
})
.finally(() => {
console.log('无论成功失败,我都会执行');
});
console.log('我先执行了');
3. Async/Await (ES2017)
这是建立在 Promise 之上的语法糖。它让异步代码的写法看起来和同步代码一样,极大地增强了代码的可读性。
-
async
:声明一个函数是异步的。这个函数总会返回一个 Promise。 -
await
:只能在async
函数内部使用。它会“等待”一个 Promise 完成并返回其结果,然后再继续执行后面的代码。
示例:
// 定义一个返回Promise的函数
function readFile() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('这是Async/Await的文件内容');
}, 1000);
});
}
// 使用 async/await
async function main() {
try {
console.log('开始读取...');
const data = await readFile(); // 等待Promise解决,拿到结果后再继续
console.log('文件内容是:', data);
const processedData = await processData(data); // 可以继续await下一个异步操作
console.log('处理后的数据:', processedData);
} catch (err) {
// 使用 try...catch 来捕获错误
console.error('出错啦:', err);
}
}
main();
console.log('我不会被await阻塞,因为main是异步的');
优点:
-
代码清晰:彻底摆脱了回调函数和
.then()
链,逻辑像同步代码一样直白。 -
错误处理:可以使用熟悉的
try...catch
语法。
其他重要的异步概念和API
-
事件循环 (Event Loop):
这是 JavaScript 实现异步的核心机制。它负责监听调用栈和任务队列(微任务、宏任务),一旦调用栈为空,就将队列中的任务推入调用栈执行。理解事件循环是深入理解异步的关键。 -
宏任务 (MacroTask) 与微任务 (MicroTask):
-
宏任务:
setTimeout
,setInterval
,setImmediate
, I/O, UI rendering。 -
微任务:
Promise.then/catch/finally
,process.nextTick
(Node.js),MutationObserver
。
执行顺序:每执行一个宏任务后,都会清空整个微任务队列。这个规则决定了Promise
总是比setTimeout
先执行。
-
-
Promise 工具方法:
-
Promise.all([promise1, promise2, ...])
:等待所有 Promise 都成功完成,返回一个结果数组。如果有一个失败,整个all
就立即失败。 -
Promise.race([promise1, promise2, ...])
:竞速,哪个 Promise 先完成(成功或失败)就返回哪个的结果。 -
Promise.allSettled([promise1, promise2, ...])
(ES2020):等待所有 Promise 都完成(无论成功失败),返回一个包含每个 Promise 结果的对象数组。 -
Promise.any([promise1, promise2, ...])
(ES2021):等待第一个成功的 Promise,如果全部失败,则返回一个聚合错误。
-
总结与最佳实践
特性 | 回调函数 (Callback) | Promise | Async/Await |
---|---|---|---|
可读性 | 差(回调地狱) | 好(链式调用) | 极好(同步风格) |
错误处理 | 麻烦(需手动传递) | 好(.catch() ) |
极好(try...catch ) |
调试 | 困难 | 较好 | 很好(错误堆栈清晰) |
流行度 | 旧代码/特定API | 现代基础 | 现代首选 |
最佳实践:
-
彻底摒弃回调地狱,不要再编写深层嵌套的回调代码。
-
首选
Async/Await
来编写新的异步逻辑,它让代码最易读、最易维护。 -
理解
Promise
是基础,因为Async/Await
是基于 Promise 的。 -
合理使用
Promise.all
等工具方法来处理并发异步操作。 -
一定要做好错误捕获,使用
.catch()
或try...catch
。
往期目录速览:
《中级前端进阶方向 第一步)JavaScript 微任务 / 宏任务机制》
《中级前端进阶方向 第二步)深入事件循环(Event Loop)》
《中级前端进阶方向 第三步)深入javascript原型链,附学习代码》
《中级前端进阶方向 第四步)深入javascript 闭包、this 、作用域提升,附学习案例源码》
《中级前端进阶方向 第五步)深入 javascript高级特性Proxy,附学习案例源码》
《中级前端进阶方向 第六步)深入 javascript 高级特性Generator生成器,附案例源码》
《中级前端进阶方向 第七步)深入 javascript 高级特性Async/Await,附源码》《中级前端进阶方向 第八步)深入 javascript 高级特性 Promise,附案例源码》
《中级前端进阶方向 第九步)深入 javascript 高级特性 模块化 与 装饰器,附案例源码》
《中级前端进阶方向 第十步)深入 浏览器性能优化(渲染机制、内存泄漏..),附案例源码》
--🖍《CSS》-----------------------------------------------------------------------------------------《中级前端进阶 第十一步) 深入CSS3 新特性,附源码,可视化演示等》
《中级前端进阶 第十二步) 深入CSS3 Flex布局,附源码,可视化演示等》
《中级前端进阶 第十三步) 深入CSS3 Grid布局,附源码,可视化演示等》
《中级前端进阶 第十四步) 深入CSS3 响应式设计,附源码,可视化演示等》
《中级前端进阶 第十五步) 深入CSS 动态主题切换,附源码,可视化演示等》
《中级前端进阶 第十六步) 深入CSS 动画,附源码,可视化演示等》
《中级前端进阶 第十七步) 深入CSS 性能优化,附源码,可视化演示等》
--🖍《框架篇》-----------------------------------------------------------------------------------------《中级前端进阶方向 框架篇第十八步) javascript proxy技术点,加可视化演示》
《中级前端进阶方向 框架篇第十九步) Vue3 响应式 可视化演示》
《中级前端进阶方向 框架篇第二十步) Virtual DOM,可视化演示》
《中级前端进阶方向 框架篇第二十一步) Diff算法,可视化演示》《中级前端进阶方向 框架篇第二十二步) Diff算法,可视化演示》
《中级前端进阶方向 框架篇第二十三步) 并行(Parallelism)》
《中级前端进阶方向 框架篇第二十四步) 并发(Concurrency)》
--🖍《延伸扩展》-----------------------------------------------------------------------------------------
《中级前端进阶方向 延伸扩展一)javascript 私有状态/工厂函数/回调中保持局部状态/防抖与节流/IIFE 捕获》《中级前端进阶方向 延伸扩展二) Promise API 概览,可视化演示页面》《中级前端进阶 延伸扩展三) CSS单位大全,附源码,可视化演示等》
《中级前端进阶方向 延伸扩展四) async/await 和事件循环的关系》
《中级前端进阶方向 延伸扩展五) CSS | GPU 加速、will-change》
《中级前端进阶方向 延伸扩展六) javascript 枚举(Enumeration)》
更多推荐
所有评论(0)