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

06-02 1675阅读

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.背景与概念
  • 2. 语法详解
    • 2.1 声明与返回值
    • 2.2 使用 await 暂停执行
    • 2.3 错误处理
    • 2.4 语法规则
    • 3. 并发与性能
      • 3.1 顺序等待 vs 并行等待
      • 3.2 并行执行优化
      • 3.2 限制并发数量
      • 4. 异步迭代
      • 5 常见问题解决方案
        • 5.1 请求重试机制
        • 5.2 竞态条件处理
        • 5.3 异步生成器
        • 5.4 处理非 Promise 值
        • 5.5 常见陷阱
        • 6. 性能优化实践
          • 6.1 内存管理
          • 6.2 优先加载优化
          • 7. 结语

            1.背景与概念

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

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

            async 函数是基于 Promise 的语法糖,用于简化异步操作的书写方式。使用 async 声明的函数会隐式返回一个 Promise,函数体内部可以通过 await 暂停执行,直到对应的 Promise 完成或抛出错误后再继续执行。

            await 操作符只能在 async 函数或模块顶层中使用,用于等待一个 Promise 解决,并将其结果作为表达式的值返回;如果 Promise 被拒绝,则会在该位置抛出异常,可配合常规的 try...catch 进行捕获处理。

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


            2. 语法详解

            2.1 声明与返回值

            async function foo() {
              return 42;
            }
            

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

            function foo() {
              return Promise.resolve(42);
            }
            

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

            2.2 使用 await 暂停执行

            async function fetchData() {
              let response = await fetch('/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 错误处理

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

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

              2.4 语法规则

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

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

              3. 并发与性能

              3.1 顺序等待 vs 并行等待

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

              let a = await task1();
              let b = await task2();
              

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

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

              3.2 并行执行优化

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

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

              3.2 限制并发数量

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


              4. 异步迭代

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

              async function* gen() {
                yield await fetchChunk(1);
                yield await fetchChunk(2);
              }
              (async () => {
                for await (let chunk of gen()) {
                  console.log(chunk);
                }
              })();
              

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


              5 常见问题解决方案

              5.1 请求重试机制

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

              async function fetchWithRetry(url, retries = 3) {
                for (let i = 0; i  setTimeout(r, 1000 * (i + 1)));
                  }
                }
              }
              

              5.2 竞态条件处理

              当多个请求并发执行时,可能因网络延迟、服务器响应速度差异等问题导致响应顺序与发送顺序不一致问题。

              更详细讲解可以查阅博主写过一篇【 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案】

              以下仅展现实现代码:

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

              5.3 异步生成器

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

              async function* asyncGenerator() {
                for (let i = 0; i  
              

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

              5.4 处理非 Promise 值

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

              let x = await 123; // 相当于 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 内存管理

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

              async function processLargeData() {
                const data = await getHugeData(); // 大数据量
                
                // 分块处理
                for (let i = 0; i  
              

              6.2 优先加载优化

              async function loadCriticalResources() {
                // 预加载非关键资源
                const nonCritical = fetch('/non-critical').then(r => r.json());
                
                // 优先处理关键资源
                const user = await fetchUser();
                const config = await fetchConfig();
                
                // 等待非关键资源
                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)完整讲解 - 从原理、应用到完整实现

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

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码