JavaScript异步编程 Async/Await 使用详解:从原理到最佳实践

JavaScript异步编程 Async/Await 使用详解:从原理到最佳实践
在这里插入图片描述
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
🌞《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

JavaScript异步编程 Async/Await 使用详解:从原理到最佳实践

1.背景与概念

在传统 JavaScript 开发中,开发者长期面临回调地狱的困扰。随着 ES6 Promise 的出现,异步代码的可读性得到改善,但链式调用依然存在嵌套问题。2017 年 ES8 正式引入的 Async/Await 语法,让异步代码第一次拥有了同步代码般的可读性。

在这里插入图片描述

async 函数是基于 Promise 的语法糖,用于简化异步操作的书写方式。使用 async 声明的函数会隐式返回一个 Promise,函数体内部可以通过 await 暂停执行,直到对应的 Promise 完成或抛出错误后再继续执行。
await 操作符只能在 async 函数或模块顶层中使用,用于等待一个 Promise 解决,并将其结果作为表达式的值返回;如果 Promise 被拒绝,则会在该位置抛出异常,可配合常规的 try...catch 进行捕获处理。

这种写法极大地提升了异步代码的可读性,使得我们可以像编写同步代码一样直观地处理异步逻辑,同时保留了后台并发执行的优势。


2. 语法详解

2.1 声明与返回值

asyncfunctionfoo(){return42;}

上例中, foo() 会返回一个已解决(fulfilled)的 Promise,其值为 42;等价于:

functionfoo(){return Promise.resolve(42);}

这是因为任何 async 函数内的返回值都会被自动封装为 Promise

2.2 使用 await 暂停执行

asyncfunctionfetchData(){let response =awaitfetch('/api/data');let data =await response.json();return data;}

代码解释:

第一行的 await fetch(...) 会暂停 fetchData 的执行,直到 fetch 返回的 Promise 完成,并将其结果赋值给 response第二行的 await response.json() 同理,等待解析 JSON 后再继续执行如果任一 Promise 拒绝,则会在该 await 位置抛出异常,可在外层使用 try...catch 捕捉。

2.3 错误处理

asyncfunctionsafeFetch(){try{let res =awaitfetch('/bad/url');let json =await res.json();return json;}catch(err){ console.error('请求失败:', err);throw err;// 可再次抛出或返回默认值}}

上述模式与同步代码中使用 try…catch 完全一致,大大简化了基于 Promise 链式 .catch() 的写法

2.4 语法规则

下面我们看看我们常用的一些使用语法

// 声明异步函数asyncfunctionfetchUser(){return{ name:'Alice', age:28};// 自动包装为Promise}// 使用箭头函数constfetchData=async()=>{const res =awaitfetch('/api/data');return res.json();};// 立即调用模式(async()=>{const data =awaitfetchData(); console.log(data);})();

3. 并发与性能

3.1 顺序等待 vs 并行等待

默认情况下,连续的 await 会串行执行:

let a =awaittask1();let b =awaittask2();

若两者互不依赖,可改为并行:

let[a, b]=await Promise.all([task1(),task2()]);

3.2 并行执行优化

通过上面 顺序等待 vs 并行等待 的介绍,通常我们可以按照以下形式来进行优化(模拟请求)

// 顺序执行(总耗时 = 各请求耗时之和)asyncfunctionserialRequests(){const res1 =awaitfetch('/api/1');const res2 =awaitfetch('/api/2');return[await res1.json(),await res2.json()];}// 并行执行(总耗时 ≈ 最慢请求耗时)asyncfunctionparallelRequests(){const[res1, res2]=await Promise.all([fetch('/api/1'),fetch('/api/2')]);returnawait Promise.all([res1.json(), res2.json()]);}

3.2 限制并发数量

在需要对大量异步任务进行限流时,可使用第三方库(如 p-limit)或自己实现简单队列,避免一次性发起过多请求导致资源竞争或网络拥堵


4. 异步迭代

ES2018 引入了 for await...of,用于遍历异步可迭代对象(如异步生成器):

asyncfunction*gen(){yieldawaitfetchChunk(1);yieldawaitfetchChunk(2);}(async()=>{forawait(let chunk ofgen()){ console.log(chunk);}})();

该语法在处理流式数据(例如文件分块下载)时非常有用


5 常见问题解决方案

5.1 请求重试机制

在外面日常开发中,会遇到请求失败需要重试的需求,来看看以下模拟代码

asyncfunctionfetchWithRetry(url, retries =3){for(let i =0; i < retries; i++){try{const res =awaitfetch(url);returnawait res.json();}catch(err){if(i === retries -1)throw err;awaitnewPromise(r=>setTimeout(r,1000*(i +1)));}}}

5.2 竞态条件处理

当多个请求并发执行时,可能因网络延迟、服务器响应速度差异等问题导致响应顺序与发送顺序不一致问题。
更详细讲解可以查阅博主写过一篇【 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案

以下仅展现实现代码:

let lastController =null;asyncfunctionsearch(query){// 取消前一个未完成的请求if(lastController) lastController.abort();const controller =newAbortController(); lastController = controller;try{const res =awaitfetch(`/api/search?q=${query}`,{ signal: controller.signal });returnawait res.json();}catch(err){if(err.name !=='AbortError')throw err;}}

5.3 异步生成器

在 ·async function*· 中,你既可以使用 ·await·,也可以使用 ·yield·,将异步任务与懒加载结合

asyncfunction*asyncGenerator(){for(let i =0; i <3; i++){awaitdelay(1000);yield i;}}

这种方式适合按需获取异步数据,提高资源利用率

5.4 处理非 Promise 值

await 后可以跟任意表达式,如果其值不是 Promise,则会被包装为立即解决的 Promise。例如:

let x =await123;// 相当于 await Promise.resolve(123)

但建议对非异步操作避免使用 await,以免误导

5.5 常见陷阱

  1. 遗忘 await:调用 async 函数但未加 await,会得到未决(pending)的 Promise 而非预期结果
  2. 在非 async 环境使用 await:仅在模块顶层或 async 函数内部可用,否则会抛语法错误
  3. Promise.all 中单个失败导致整体失败:若需要容忍部分失败,可对内部 Promise 使用 .catch() 处理,避免整体拒绝
  4. 滥用并发:同时发起过多网络请求可能触发限流或阻塞,建议根据场景调整并发策略

6. 性能优化实践

博主这里例举两个优化的案例:内存管理以及优先加载优化

6.1 内存管理

常见一些大量数据的获取下载

asyncfunctionprocessLargeData(){const data =awaitgetHugeData();// 大数据量// 分块处理for(let i =0; i < data.length; i +=1000){const chunk = data.slice(i, i +1000);awaitprocessChunk(chunk); data[i]=null;// 及时释放内存}}

6.2 优先加载优化

asyncfunctionloadCriticalResources(){// 预加载非关键资源const nonCritical =fetch('/non-critical').then(r=> r.json());// 优先处理关键资源const user =awaitfetchUser();const config =awaitfetchConfig();// 等待非关键资源const data =await nonCritical;return{ user, config, data };}

7. 结语

Async/Await 的引入彻底改变了 JavaScript 异步编程的面貌。通过本文的讲解,相信小伙伴们可以掌握 Async/Await 的使用精髓,将使您的 JavaScript 代码在保持高性能的同时,获得质的可读性和可维护性提升。

如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家一键三连给博主一点点鼓励!


前端技术专栏回顾:

01【前端技术】 ES6 介绍及常用语法说明
02【前端技术】标签页通讯localStorage、BroadcastChannel、SharedWorker的技术详解
03 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案
04 前端开发中深拷贝的循环引用问题:从问题复现到完美解决
05 前端AJAX请求上传下载进度监控指南详解与完整代码示例
06 TypeScript 进阶指南 - 使用泛型与keyof约束参数
07 前端实现视频文件动画帧图片提取全攻略 - 附完整代码样例
08 前端函数防抖(Debounce)完整讲解 - 从原理、应用到完整实现

Read more

SpringBoot详解

文章目录 * 概览 * 与Spring的区别 * 创建SpringBoot项目 * SpringBoot常用注解 * SpringBoot自动配置 * @SpringBootConfiguration * @EnableAutoConfiguration * SpringBoot配置管理 * SpringBoot嵌入式服务器 * SpringBoot测试 概览 SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。SpringBoot提供了一种新的编程范式,可以更加快速便捷地开发Spring项目,在开发过程当中可以专注于应用程序本身的功能开发,而无需在Spring配置上花太大的工夫。 SpringBoot基于Sring4进行设计,继承了原有Spring框架的优秀基因。SpringBoot准确的说并不是一个框架,而是一些类库的集合。maven或者gradle项目导入相应依赖即可使用 SpringBoot,而无需自行管

By Ne0inhk
告别小白!吃透 MySQL 基本查询,看这一篇就够了

告别小白!吃透 MySQL 基本查询,看这一篇就够了

🔥海棠蚀omo:个人主页                 ❄️个人专栏:《初识数据结构》,《C++:从入门到实践》,《Linux:从零基础到实践》,《Linux网络:从不懂到不会》,《MySQL:新手入门指南》                 ✨追光的人,终会光芒万丈 博主简介: 目录 一.Create 1.1替换 二.Retrieve 2.1SELECT列 2.1.1全列查询 2.1.2指定列查询 2.1.3查询字段为表达式 2.1.4为查询结果指定别名 2.1.5结果去重 2.2WHERE条件 2.2.1英语不及格的同学及英语成绩 2.2.2语文成绩在[80,90]分的同学及语文成绩

By Ne0inhk
Flutter 组件 okay 的适配 鸿蒙Harmony 深度进阶 - 驾驭异步结果链式融合、实现鸿蒙端分布式业务逻辑解耦与精密审计方案

Flutter 组件 okay 的适配 鸿蒙Harmony 深度进阶 - 驾驭异步结果链式融合、实现鸿蒙端分布式业务逻辑解耦与精密审计方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 okay 的适配 鸿蒙Harmony 深度进阶 - 驾驭异步结果链式融合、实现鸿蒙端分布式业务逻辑解耦与精密审计方案 前言 在前文中,我们探讨了 okay 在鸿蒙(OpenHarmony)端实现基础 Result 模式包装的实战。但在真正的“分布式微服务聚合”、“高并发资产对账”以及“具备自愈能力的 IoT 指令链”场景中。简单的 ok() 与 err() 判定往往不足以支撑起复杂的业务全景。面对需要同时并行发起 3 个 API 请求,并要求在“所有请求均成功时执行合并、任一请求失败时执行局部逻辑路由”的高阶需求。如果缺乏一套完善的异步结果映射与多级逻辑聚合机制。不仅会导致异步回调地狱(Callback Hell)在

By Ne0inhk
MySQL 大数据处理优化与分布式架构探索

MySQL 大数据处理优化与分布式架构探索

MySQL 大数据处理优化与分布式架构探索 在数据爆炸式增长的时代,MySQL 作为一款流行的开源关系型数据库管理系统,如何在大数据处理场景下保持高效与稳定,成为了众多开发者和数据库管理员关注的焦点。本文将深入探讨 MySQL 大数据处理优化与分布式架构的实现与应用,帮助读者更好地应对高并发和大数据量的挑战。 一、MySQL 大数据处理面临的挑战 随着业务的发展和用户数量的增长,MySQL 数据库面临的数据量急剧增加,这对数据库的性能和扩展性提出了更高要求。传统的单机 MySQL 数据库在处理大规模数据时,往往会遇到性能瓶颈,如查询速度慢、写入压力大、存储能力不足等问题。因此,如何优化 MySQL 大数据处理,成为了一个亟待解决的问题。 二、MySQL 大数据处理优化策略 1. 索引优化 索引是 MySQL 查询优化的关键。合理的索引设计可以显著提高查询速度。在大数据量场景下,应重点关注以下几点: * 选择合适的索引类型:根据查询需求选择合适的索引类型,如主键索引、唯一索引、普通索引、复合索引等。[9] * 避免索引失效:注意查询条件中的数据类型匹配、

By Ne0inhk