news 2026/4/18 8:25:16

JavaScript 异步编程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 应用至关重要!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 8:24:43

java——接口——非抽象方法

文章目录背景Java 接口与抽象类详解一、接口实现与部分实现的问题问题描述答案解决方案关键概念区分类型对比表二、接口中的 default 方法(第一次见到)问题描述答案接口方法的完整规则&#xff08;Java 8&#xff09;为什么引入 default 方法&#xff1f;完整示例三、接口与抽象…

作者头像 李华
网站建设 2026/4/18 8:23:12

AMD Ryzen 硬件调试实战:从零掌握SMUDebugTool的精准控制系统

AMD Ryzen 硬件调试实战&#xff1a;从零掌握SMUDebugTool的精准控制系统 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: htt…

作者头像 李华
网站建设 2026/4/18 8:21:02

WebPlotDigitizer:如何快速从科研图表中提取数据的终极指南

WebPlotDigitizer&#xff1a;如何快速从科研图表中提取数据的终极指南 【免费下载链接】WebPlotDigitizer Computer vision assisted tool to extract numerical data from plot images. 项目地址: https://gitcode.com/gh_mirrors/we/WebPlotDigitizer 在科研工作中&a…

作者头像 李华
网站建设 2026/4/18 8:20:42

解决Tkinter和Matplotlib的无限循环问题

在使用Python进行图形界面编程时,经常会遇到将Matplotlib的图表嵌入到Tkinter窗口中的需求。然而,这个过程中可能会遇到一些棘手的问题,比如程序进入无限循环,导致无法正常结束进程。今天我们就来详细讨论一下这个问题,并通过实例展示如何解决。 问题背景 在编写Tkinter…

作者头像 李华
网站建设 2026/4/18 8:20:03

26-4-17 数据结构作业:用栈解决迷宫问题

1.问题描述 已知一个 66 的迷宫&#xff0c;可将其视作在一个坐标系中&#xff0c;令起点 (1,1)&#xff0c;终点 (4,4)&#xff0c;墙&#xff1a;1、路&#xff1a;0&#xff0c;要求用队列实现最短路径搜索。 2.算法思路 题目要求使用队列&#xff08;先进先出&#xff09…

作者头像 李华
网站建设 2026/4/18 8:19:40

解决MVC Web API中的级联保存问题

在开发MVC Web API应用时,处理模型之间的关系是一个常见的挑战,尤其是在使用Entity Framework时。今天我们来探讨一个具体的案例,分析为什么在添加城市数据时,用户数据也会被意外保存到数据库中,并提供解决方案。 案例背景 假设我们有一个城市管理系统,包含以下模型: …

作者头像 李华