当前位置: 首页 > news >正文

JavaScript 异步编程

JavaScript 异步编程学习笔记

JavaScript 是单线程语言,这意味着它同一时间只能执行一段代码。为了解决耗时操作(如网络请求、文件读写、定时器)阻塞主线程的问题,JavaScript 发展出了强大的异步编程模型。


1.核心概念

概念说明
同步 (Synchronous)代码按顺序一行行执行,前一个任务完成前,后一个任务无法开始。
异步 (Asynchronous)耗时任务被挂起,主线程继续执行后续代码;任务完成后通过回调通知主线程。
事件循环 (Event Loop)JavaScript 运行时(浏览器/Node.js)的核心机制,负责将“已完成”的异步任务回调推入执行队列,等待主线程空闲时执行。
宏任务 (MacroTask)setTimeout,setInterval,I/O,UI 渲染
微任务 (MicroTask)Promise.then,MutationObserver,queueMicrotask

执行顺序规则:同步代码 > 微任务 > 宏任务。


2.异步编程的演进历程

(1) 回调函数 (Callback) - 早期方案

将函数作为参数传递给另一个函数,在任务完成后执行。

// 嵌套回调(回调地狱)fs.readFile('file1.txt',(err,data1)=>{if(err)returnconsole.error(err);fs.readFile('file2.txt',(err,data2)=>{if(err)returnconsole.error(err);fs.readFile('file3.txt',(err,data3)=>{// ... 层层嵌套,难以维护console.log(data1+data2+data3);});});});
  • 缺点:代码嵌套深(回调地狱),错误处理困难,逻辑不清晰。
(2) Promise - ES6 标准

将异步操作封装为一个对象,代表“未来”的某个结果。

状态

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)
constfetchData=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>{constsuccess=true;if(success){resolve("数据获取成功");}else{reject("数据获取失败");}},1000);});};// 链式调用fetchData().then(result=>{console.log(result);return"下一步";}).then(next=>{console.log(next);}).catch(error=>{console.error("发生错误:",error);}).finally(()=>{console.log("无论成功失败都会执行");});
  • 优点:链式调用,避免嵌套;统一错误处理 (catch)。
  • 缺点:状态一旦改变不可逆;无法中途取消;错误堆栈不清晰。
(3) Async/Await - ES2017 (现代最佳实践)

基于 Promise 的语法糖,让异步代码看起来像同步代码。

asyncfunctiongetData(){try{// 等待 Promise 完成constresult=awaitfetchData();console.log(result);constnext=awaitPromise.resolve("下一步");console.log(next);return"完成";}catch(error){console.error("发生错误:",error);}finally{console.log("清理工作");}}getData();
  • 优点:代码清晰易读;错误处理符合同步习惯 (try...catch);调试方便。
  • 注意await只能在async函数内部使用。

3.Event Loop (事件循环) 执行顺序

理解执行顺序是掌握异步编程的关键。

console.log('1. 同步开始');setTimeout(()=>{console.log('4. 宏任务 (setTimeout)');},0);Promise.resolve().then(()=>{console.log('3. 微任务 (Promise)');});console.log('2. 同步结束');// 输出顺序:// 1. 同步开始// 2. 同步结束// 3. 微任务 (Promise)// 4. 宏任务 (setTimeout)

执行流程

  1. 执行所有同步代码
  2. 清空微任务队列(Promise.then 等)。
  3. 执行一个宏任务(setTimeout 等)。
  4. 执行 UI 渲染(浏览器)。
  5. 回到步骤 2,循环往复。

4.常用异步 API

(1)fetchAPI (网络请求)

现代浏览器替代XMLHttpRequest的标准。

asyncfunctiongetUserData(userId){try{constresponse=awaitfetch(`https://api.example.com/users/${userId}`);if(!response.ok){thrownewError(`HTTP 错误!状态码:${response.status}`);}constdata=awaitresponse.json();// 解析 JSONconsole.log(data);returndata;}catch(error){console.error("请求失败:",error);}}
(2)Promise.all(并发处理)

等待多个 Promise 全部成功,或其中一个失败。

constp1=fetch('/api/user');constp2=fetch('/api/posts');Promise.all([p1,p2]).then(([userRes,postsRes])=>{returnPromise.all([userRes.json(),postsRes.json()]);}).then(([user,posts])=>{console.log("全部加载完成",user,posts);}).catch(err=>{console.error("任意一个失败",err);});
(3)Promise.race(竞速)

只要有一个 Promise 完成(无论成功失败),立即返回。

Promise.race([fetch('/api/slow'),newPromise((_,reject)=>setTimeout(()=>reject("超时"),5000))]).then(data=>console.log(data)).catch(err=>console.error(err));// 5秒后触发超时
(4)async/await并发优化

使用Promise.all配合await实现并发。

asyncfunctionfetchAll(){// 并发执行(同时发起请求)const[users,posts]=awaitPromise.all([fetch('/api/users').then(r=>r.json()),fetch('/api/posts').then(r=>r.json())]);console.log(users,posts);}

5.常见陷阱与最佳实践

陷阱 1:在循环中误用await
// ❌ 串行执行(慢)for(consturlofurls){constres=awaitfetch(url);// 必须等上一个完成constdata=awaitres.json();console.log(data);}// ✅ 并发执行(快)constpromises=urls.map(url=>fetch(url).then(r=>r.json()));constresults=awaitPromise.all(promises);
陷阱 2:忘记await
asyncfunctiongetData(){constres=fetch('/api/data');// 忘记 awaitconsole.log(res);// 输出 Promise 对象,而不是数据}
陷阱 3:顶层await(Top-level await)

在 ES 模块 (.mjs<script type="module">) 中,可以直接在模块顶层使用await,无需包裹在函数中。

// module.jsconstdata=awaitfetch('/api/data');exportconstjson=awaitdata.json();

6.总结对比

特性CallbackPromiseAsync/Await
代码可读性差 (嵌套)中 (链式)优 (同步风格)
错误处理分散在每个回调catchtry...catch
调试难度难 (堆栈断裂)
执行控制困难中等
推荐场景遗留代码简单链式调用复杂业务逻辑

最佳实践建议

  1. 优先使用async/await,代码最清晰。
  2. 处理多个独立请求时,使用Promise.all实现并发
  3. 处理依赖请求时(B 依赖 A 的结果),使用串行await
  4. 始终使用try...catch捕获异步错误。
  5. 避免在循环中串行await,除非必须按顺序执行。

异步编程是 JavaScript 的精髓,掌握它对于开发高性能 Web 应用至关重要!

http://www.jsqmd.com/news/659857/

相关文章:

  • 深入解析二维随机变量的期望E(XY)与方差D(XY)计算实例
  • AI编程提效的真实瓶颈:不是工具不行,是需求没说清楚
  • WebPlotDigitizer数据提取指南:3步从图表图像获取精准数值的完整教程
  • 3分钟快速上手:本地视频硬字幕提取终极指南
  • ComfyUI-Manager终极指南:如何5分钟快速管理所有自定义节点
  • Bili2text:一键免费将B站视频转为文字稿的高效工具
  • 储能系统出海欧洲:基于CE合规规范的边缘计算网关数据架构与代码实践
  • 别再只会用audioread了!手把手教你用MATLAB直接解析WAV文件头(附完整代码)
  • Face3D.ai Pro在教育领域的应用:3D解剖学教学工具
  • 如何快速解密微信聊天记录:WechatDecrypt工具的完整实战指南
  • 【JS-Node】node.js环境安装及使用
  • Pixel Language Portal 助力 Java 面试:SpringBoot 八股文智能问答与模拟面试
  • 八股(六)操作系统
  • ClawdBot应用教程:本地AI助手权限管理,devices命令全解析
  • 从华数杯到数学建模:手把手教你用CCR模型搞定‘脱贫绩效评价’这类题
  • WebPlotDigitizer:5分钟学会图表数据提取,科研效率提升700%
  • 如何高效分析虚幻引擎Pak文件:UnrealPakViewer终极指南
  • 3步解锁微信网页版:告别“无法登录“的终极浏览器插件方案
  • 为什么你的C盘总是爆满?3个步骤让Windows Cleaner帮你彻底解决
  • UnrealPakViewer终极指南:5个简单步骤掌握虚幻引擎Pak文件分析
  • 解决MVC Web API中的级联保存问题
  • 10个宝藏资源网站盘点
  • 阿里 HappyOyster :AI 交互的下一个试金石?
  • 终极指南:3步掌握Wallpaper Engine资源提取与转换神器
  • DeepSeek-R1-Distill-Qwen-7B多场景应用:Ollama本地部署后支持教育领域习题讲解与解题步骤生成
  • Phi-4-mini-reasoning 3.8B:开源轻量模型在多样化任务上的综合能力展示
  • 【雷达成像】主动式毫米波安检成像Matlab实现
  • 米拉-魁北克AI研究所教会小模型“聪明干活“
  • 如何5分钟完成视频字幕提取:Video-subtitle-extractor完整解决方案指南
  • 免费开源!AMD Ryzen处理器底层调试终极指南:SMUDebugTool让你的硬件性能触手可及