详解ES6之async+await 同步/异步方案

发布时间 - 2026-01-11 03:19:23    点击率:

异步编程一直是JavaScript 编程的重大事项。关于异步方案, ES6 先是出现了 基于状态管理的 Promise,然后出现了 Generator 函数 + co 函数,紧接着又出现了 ES7 的 async + await 方案。

本文力求以最简明的方式来疏通 async + await。

异步编程的几个场景

先从一个常见问题开始:一个for 循环中,如何异步的打印迭代顺序?

我们很容易想到用闭包,或者 ES6 规定的 let 块级作用域来回答这个问题。

for (let val of [1, 2, 3, 4]) {
  setTimeout(() => console.log(val),100);
}
// => 预期结果依次为:1, 2, 3, 4

这里描述的是一个均匀发生的的异步,它们被依次按既定的顺序排在异步队列中等待执行。

如果异步不是均匀发生的,那么它们被注册在异步队列中的顺序就是乱序的。

for (let val of [1, 2, 3, 4]) {
  setTimeout(() => console.log(val), 100 * Math.random());
}
// => 实际结果是随机的,依次为:4, 2, 3, 1

返回的结果是乱序不可控的,这本来就是最为真实的异步。但另一种情况是,在循环中,如果希望前一个异步执行完毕、后一个异步再执行,该怎么办?

for (let val of ['a', 'b', 'c', 'd']) {
  // a 执行完后,进入下一个循环
  // 执行 b,依此类推
}

这不就是多个异步 “串行” 吗!

在回调 callback 嵌套异步操作、再回调的方式,不就解决了这个问题!或者,使用 Promise + then() 层层嵌套同样也能解决问题。但是,如果硬是要将这种嵌套的方式写在循环中,还恐怕还需费一番周折。试问,有更好的办法吗?

异步同步化方案

试想,如果要去将一批数据发送到服务器,只有前一批发送成功(即服务器返回成功的响应),才开始下一批数据的发送,否则终止发送。这就是一个典型的 “for 循环中存在相互依赖的异步操作” 的例子。

明显,这种 “串行” 的异步,实质上可以当成同步。它和乱序的异步比较起来,花费了更多的时间。按理说,我们希望程序异步执行,就是为了 “跳过” 阻塞,较少时间花销。但与之相反的是,如果需要一系列的异步 “串行”,我们应该怎样很好的进行编程?

对于这个 “串行” 异步,有了 ES6 就非常容易的解决了这个问题。

async function task () {
  for (let val of [1, 2, 3, 4]) {
    // await 是要等待响应的
    let result = await send(val);
    if (!result) {
      break;
    }
  }
}
task();

从字面上看,就是本次循环,等有了结果,再进行下一次循环。因此,循环每执行一次就会被暂停(“卡住”)一次,直到循环结束。这种编码实现,很好的消除了层层嵌套的 “回调地狱” 问题,降低了认知难度。

这就是异步问题同步化的方案。关于这个方案,如果说 Promise 主要解决的是异步回调问题,那么 async + await 主要解决的就是将异步问题同步化,降低异步编程的认知负担。

async + await “外异内同”

早先接触这套 API 时,看着繁琐的文档,一知半解的认为 async + await 主要用来解决异步问题同步化的。

其实不然。从上面的例子看到:async 关键字声明了一个 异步函数,这个 异步函数 体内有一行 await 语句,它告示了该行为同步执行,并且与上下相邻的代码是依次逐行执行的。

将这个形式化的东西再翻译一下,就是:

1、async 函数执行后,总是返回了一个 promise 对象
2、await 所在的那一行语句是同步的

其中,1 说明了从外部看,task 方法执行后返回一个 Promise 对象,正因为它返回的是 Promise,所以可以理解task 是一个异步方法。毫无疑问它是这样用的:

task().then((val) => {alert(val)})
   .then((val) => {alert(val)})

2 说明了在 task 函数内部,异步已经被 “削” 成了同步。整个就是一个执行稍微耗时的函数而已。

综合 1、2,从形式上看,就是 “task 整体是一个异步函数,内部整个是同步的”,简称“外异内同”。

整体是一个异步函数 不难理解。在实现上,我们不妨逆向一下,语言层面让async关键字调用时,在函数执行的末尾强制增加一个promise 反回:

async fn () {
  let result;
  // ...
  //末尾返回 promise
  return isPromise(result)? 
      result : Promise.resolve(undefined);
}

内部是同步的 是怎么做到的?实际上 await 调用,是让后边的语句(函数)做了一个递归执行,直到获取到结果并使其 状态 变更,才会 resolve 掉,而只有 resolve 掉,await 那一行代码才算执行完,才继续往下一行执行。所以,尽管外部是一个大大的 for 循环,但是整个 for 循环是依次串行的。

因此,仅从上述框架的外观出发,就不难理解 async + await 的意义。使用起来也就这么简单,反而 Promise 是一个必须掌握的基础件。

秉承本次《重读 ES6》系列的原则,不过多追求理解细节和具体实现过程。我们继续巩固一下这个 “形式化” 的理解。

async + await 的进一步理解

有这样的一个异步操作 longTimeTask,已经用 Promise 进行了包装。借助该函数进行一系列验证。

const longTimeTask = function (time) {
 return new Promise((resolve, reject) => {
  setTimeout(()=>{
   console.log(`等了 ${time||'xx'} 年,终于回信了`);
   resolve({'msg': 'task done'});
  }, time||1000)
 })
}

async 函数的执行情况

如果,想查看 async exec1 函数的返回结果,以及 await 命令的执行结果:

const exec1 = async function () {
 let result = await longTimeTask();
 console.log('result after long time ===>', result);
}
// 查看函数内部执行顺序
exec1();
// => 等了 xx 年,终于回信了
// => result after long time ===> Object {msg: "task done"}

//查看函数总体返回值
console.log(exec1());
// => Promise {[[PromiseStatus]]: "pending",...}
// => 同上

以上 2 步执行,清晰的证明了 exec1 函数体内是同步、逐行逐行执行的,即先执行完异步操作,然后进行 console.log() 打印。而 exec1() 的执行结果就直接是一个 Promise,因为它最先会蹦出来一串 Promise ...,然后才是 exec1 函数的内部执行日志。

因此,所有验证,完全符合 整体是一个异步函数,内部整个是同步的 的总结。

await 如何执行其后语句?

回到 await ,看看它是如何执行其后边的语句的。假设:让 longTimeTask() 后边直接带 then() 回调,分两种情况:

1)then() 中不再返回任何东西
2) then() 中继续手动返回另一个 promise

const exec2 = async function () {
 let result = await longTimeTask().then((res) => {
  console.log('then ===>', res.msg);
  res.msg = `${res.msg} then refrash message`;
  // 注释掉这条 return 或 手动返回一个 promise
  return Promise.resolve(res);
 });
 console.log('result after await ===>', result.msg);
}
exec2();
// => 情况一 TypeError: Cannot read property 'msg' of undefined
// => 情况二 正常

首先,longTimeTask() 加上再多得 then() 回调,也不过是放在了它的回调列队 queue 里了。也就是说,await 命令之后始终是一条 表达式语句,只不过上述代码书写方式比较让人迷惑。(比较好的实践建议是,将 longTimeTask 方法身后的 then() 移入 longTimeTask 函数体封装起来)

其次,手动返回另一个 promise 和什么也不返回,关系到 longTimeTask() 方法最终 resolve 出去的内容不一样。换句话说,await 命令会提取其后边的promise 的 resolve 结果,进而直接导致 result 的不同。

值得强调的是,await 命令只认 resolve 结果,对 reject 结果报错。不妨用以下的 return 语句替换上述 return 进行验证。

return Promise.reject(res);

最后

其实,关于异步编程还有很多可以梳理的,比如跨模块的异步编程、异步的单元测试、异步的错误处理以及什么是好的实践。All in all, 限于篇幅,不在此汇总了。最后,async + await 确实是一个很优雅的方案。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# async  # await  # 同步  # 异步  # ES6  # vue如何使用async、await实现同步请求  # async/await让异步操作同步执行的方法详解  # 微信小程序中使用Async-await方法异步请求变为同步请求方法  # js异步之async和await实现同步写法  # 是一个  # 的是  # 回调  # 这个问题  # 很好  # 出现了  # 它是  # 上看  # 因为它  # 等了  # 说明了  # 递归  # 依次为  # 体内  # 解决了  # 看着  # 几个  # 就会  # 也不  # 让人 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel Seeder怎么填充数据_Laravel数据库填充器的使用方法与技巧  韩国服务器如何优化跨境访问实现高效连接?  Laravel如何实现本地化和多语言支持_Laravel多语言配置与翻译文件管理  如何撰写建站申请书?关键要点有哪些?  详解Android——蓝牙技术 带你实现终端间数据传输  Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】  Linux系统运维自动化项目教程_Ansible批量管理实战  高防服务器租用指南:配置选择与快速部署攻略  Laravel怎么配置S3云存储驱动_Laravel集成阿里云OSS或AWS S3存储桶【教程】  Laravel与Inertia.js怎么结合_使用Laravel和Inertia构建现代单页应用  电商网站制作多少钱一个,电子商务公司的网站制作费用计入什么科目?  大型企业网站制作流程,做网站需要注册公司吗?  夸克浏览器网页跳转延迟怎么办 夸克浏览器跳转优化  Laravel中间件如何使用_Laravel自定义中间件实现权限控制  图册素材网站设计制作软件,图册的导出方式有几种?  如何在万网自助建站平台快速创建网站?  消息称 OpenAI 正研发的神秘硬件设备或为智能笔,富士康代工  ,网页ppt怎么弄成自己的ppt?  制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?  如何在IIS服务器上快速部署高效网站?  如何快速生成高效建站系统源代码?  软银砸40亿美元收购DigitalBridge 强化AI资料中心布局  rsync同步时出现rsync: failed to set times on “xxxx”: Operation not permitted  如何快速查询网址的建站时间与历史轨迹?  Python数据仓库与ETL构建实战_Airflow调度流程详解  深圳网站制作培训,深圳哪些招聘网站比较好?  Android自定义listview布局实现上拉加载下拉刷新功能  浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】  简单实现jsp分页  湖南网站制作公司,湖南上善若水科技有限公司做什么的?  智能起名网站制作软件有哪些,制作logo的软件?  Laravel怎么进行浏览器测试_Laravel Dusk自动化浏览器测试入门  Laravel如何正确地在控制器和模型之间分配逻辑_Laravel代码职责分离与架构建议  Laravel如何生成URL和重定向?(路由助手函数)  米侠浏览器网页背景异常怎么办 米侠显示修复  绝密ChatGPT指令:手把手教你生成HR无法拒绝的求职信  IOS倒计时设置UIButton标题title的抖动问题  Laravel怎么进行数据库回滚_Laravel Migration数据库版本控制与回滚操作  javascript中数组(Array)对象和字符串(String)对象的常用方法总结  Laravel如何处理JSON字段的查询和更新_Laravel JSON列操作与查询技巧  浅谈Javascript中的Label语句  Claude怎样写结构化提示词_Claude结构化提示词写法【教程】  如何实现建站之星域名转发设置?  佛山企业网站制作公司有哪些,沟通100网上服务官网?  Laravel如何配置Horizon来管理队列?(安装和使用)  Java Adapter 适配器模式(类适配器,对象适配器)优缺点对比  如何快速上传建站程序避免常见错误?  用v-html解决Vue.js渲染中html标签不被解析的问题  Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】  北京网站制作公司哪家好一点,北京租房网站有哪些?