JavaScript异步编程 Async/Await 使用从原理到最佳实践记录

 更新时间:2025年05月26日 14:44:31   作者:Micro麦可乐  
Async/Await 的引入彻底改变了 JavaScript 异步编程的面貌,通过本文的讲解,相信小伙伴们可以掌握 Async/Await 的使用精髓,将使您的 JavaScript 代码在保持高性能的同时,获得质的可读性和可维护性提升,感兴趣的朋友一起看看吧

1.背景与概念

在传统 JavaScript 开发中,开发者长期面临回调地狱的困扰。随着 ES6 Promise 的出现,异步代码的可读性得到改善,但链式调用依然存在嵌套问题。2017 年 ES8 正式引入的 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 < retries; i++) {
    try {
      const res = await fetch(url);
      return await res.json();
    } catch (err) {
      if (i === retries - 1) throw err;
      await new Promise(r => 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 < 3; i++) {
    await delay(1000);
    yield i;
  }
}

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

5.4 处理非 Promise 值

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

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

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

5.5 常见陷阱

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

6. 性能优化实践

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

6.1 内存管理

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

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

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 代码在保持高性能的同时,获得质的可读性和可维护性提升。

到此这篇关于JavaScript异步编程 Async/Await 使用详解:从原理到最佳实践的文章就介绍到这了,更多相关js异步编程Async/Await 使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • wavesurfer.js绘制音频波形图的实现

    wavesurfer.js绘制音频波形图的实现

    这篇文章主要介绍了wavesurfer.js绘制音频波形图的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • TypeScript利用TS封装Axios实战

    TypeScript利用TS封装Axios实战

    这篇文章主要介绍了TypeScript利用TS封装Axios实战,TypeScript封装一遍Axios,能进一步巩固TypeScript的基础知识,需要的小伙伴可以参考一下
    2022-06-06
  • JavaScript限定复选框的选择个数示例代码

    JavaScript限定复选框的选择个数示例代码

    有10个复选框,用户最多只能勾选3个,否则就灰掉所有复选框,具体实现思路及代码如下,感兴趣的朋友可以参考下,希望对大家有所帮助
    2013-08-08
  • javascript iframe中打开文件,并检测iframe存在否

    javascript iframe中打开文件,并检测iframe存在否

    从iframe中打开文件,并检测iframe存在否如果说只是检测页面存在否,直接设置target用伪协议就可以解决了...
    2008-12-12
  • Js中pick函数的具体使用

    Js中pick函数的具体使用

    本文主要介绍了Js中pick函数的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-09-09
  • js拖拽功能实现代码解析

    js拖拽功能实现代码解析

    这篇文章主要为大家详细介绍了js实现拖拽功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • 原生js实现简易计算器

    原生js实现简易计算器

    这篇文章主要为大家详细介绍了原生js实现简易计算器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • 使用js修改客户端注册表的方法

    使用js修改客户端注册表的方法

    这篇文章介绍了使用js修改客户端注册表的方法,有需要的朋友可以参考一下
    2013-08-08
  • JS/jQuery实现超简单的Table表格添加,删除行功能示例

    JS/jQuery实现超简单的Table表格添加,删除行功能示例

    这篇文章主要介绍了JS/jQuery实现超简单的Table表格添加,删除行功能,结合实例形式详细分析了JS与jQuery针对Table表格添加,删除行功能的相关实现技巧,需要的朋友可以参考下
    2019-07-07
  • JavaScript实现简单的计算器

    JavaScript实现简单的计算器

    这篇文章主要为大家详细介绍了JavaScript实现简单的计算器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-01-01

最新评论