有了async/await,你可以丢掉promise链了

2019 年 1 月 02 日

有了async/await,你可以丢掉promise链了

异步函数可能会一直存在,但有些人认为 async/await 可能会被抛弃。


为什么?


一个常见的误解是 async/await 和 promise 是完全不同的东西。


但其实 async/await 是基于 promise 的。


不要因为你使用了 promise 就被 promise 链给野蛮绑架了。


在本文中,我们将了解 async/await 如何让开发人员的生活变得更轻松,以及为什么要停止使用 promise 链。


让我们来看看一个 promise 链的例子:


getIssue()  .then(issue => getOwner(issue.ownerId))  .then(owner => sendEmail(owner.email, 'Some text'))
复制代码


现在让我们看看 async/await 的等效代码:


const issue = await getIssue()const owner = await getOwner(issue.ownerId)await sendEmail(owner.email, 'Some text')
复制代码


它看起来就像简单的语法糖,对吗?


与大多数人一样,我发现自己的代码看起来很简单、干净、易于阅读。但是,在修改代码时,似乎比预期的困难一些。


但这一点也不奇怪,这正是 promise 链的问题所在。下面让我们看看这是为什么。


易于阅读,易于维护


假设我们需要对之前的代码做出一个很小的修改(例如,我们需要在电子邮件内容中提及问题编号,比如“Some text #issue-number”)。


我们该怎么做?对于 async/await 版本,改起来很简单:


const issue = await getIssue()const owner = await getOwner(issue.ownerId)await sendEmail(owner.email, `Some text #${issue.number}`) // tiny change here

复制代码


前两行不受影响,第三行只需要稍微改动一点点。


那么 promise 链版本呢?


在.then()中,我们可以访问 owner,但不能访问 issue。看看,promise 链从这里开始就变得有点混乱了。我们可以试着这样修改:


getIssue()  .then(issue => {    return getOwner(issue.ownerId)      .then(owner => sendEmail(owner.email, `Some text #${issue.number}`))  })
复制代码


正如你所看到的,一个小的调整就需要修改好几行代码(如 getOwner(issue.ownerId))。


代码在不断发生变化


在开发新功能时尤其如此。例如,如果我们需要将异步调用 getSettings()返回的结果包含在电子邮件内容中,该怎么办?


它可能看起来像这样:


const settings = await getSettings() // we added thisconst issue = await getIssue()const owner = await getOwner(issue.ownerId)await sendEmail(owner.email,  `Some text #${issue.number}. ${settings.emailFooter}`) // minor change here

复制代码


如果使用 promise 链该怎样实现?可能是这样:


Promise.all([getIssue(), getSettings()])  .then(([issue, settings]) => {    return getOwner(issue.ownerId)      .then(owner => sendEmail(owner.email,        `Some text #${issue.number}. ${settings.emailFooter}`))  })
复制代码


但是,对我来说,这些代码显得有点乱。每当我们需要做出修改时,都需要修改很多代码,这实在太恶心了!


因为我不想再嵌套 then()调用,我可以并行地调用 getIssue()和 getSettings(),所以我使用了 Promise.all(),然后进行一些解构。确实,这个版本与 await 版本相比更好,因为它可以并行运行 ,但它仍然难以阅读。


我们是否可以优化 await 版本,让它可以并行运行而不需要牺牲代码的可读性?让我们来看看:


const settings = getSettings() // we don't await hereconst issue = await getIssue()const owner = await getOwner(issue.ownerId)await sendEmail(owner.email,  `Some text #${issue.number}. ${(await settings).emailFooter}`) // we do it here
复制代码


我删除了 settings 右侧的 await,并在 sendEmail()前面加上了 await。我创建了一个 promise,但在需要用到这个值之前不需要等待。与此同时,其他代码可以并行运行。就这么简单!


你不需要 Promise.all()


我已经演示了如何在不使用 Promise.all()的情况下轻松有效地并行运行 promise。这意味着你不再需要 Promise.all()了,对吧。


有些人可能会争辩说,还有一个情况,也就是当你有一个值数组时,你需要将它映射到一个 promise 数组。例如,你有一个要读取的文件名的数组,或者你需要下载的 URL 的数组,等等。


我认为他们错了。我的建议是使用外部库来处理并发。例如,我会使用 bluebird 中的 Promise.map(),因为它支持设置并发限制。如果我要下载 N 个文件,可以指定同时下载的文件个数不超过 M 个。


你可以在任何地方使用 await


async/await 可以帮你简化你要做的事情。想象一下,如果使用 promise 链,下面这些表达式有多复杂。但是如果使用 async/await,它们就会简单得多。


const value = await foo() || await bar()
const value = calculateSomething(await foo(), await bar())
复制代码


还说服不了你?


假设你对代码可阅读性和易维护性不感兴趣,相反,你更喜欢复杂性,那么好吧。


在代码中使用 promise 链时,开发者每次在调用 then()时都会创建新函数。这会占用更多内存,而且这些函数总是处在另一个上下文中。因此,这些函数变成了闭包,这使垃圾回收变得更加困难。此外,这些匿名函数通常会污染堆栈跟踪。


现在,我们讨论的是堆栈跟踪:现在有一个提议用于为异步函数实现更好的堆栈跟踪。


只要开发人员坚持只使用异步函数和异步生成器,并且不会手动编写 promise 代码,因为如果使用了 promise 链,就无法实现更好的堆栈跟踪。


这也是总是使用 async/await 的另一个原因!


如何迁移


首先:开始使用异步函数并停止使用 promise 链。


其次,你可能已经发现 Visual Studio Code 可以非常方便地帮你实现迁移。


视频地址:https://twitter.com/umaar/status/1045655069478334464


结论


  • async/await已得到广泛支持,除非你需要支持IE。

  • async/await代码具有更好的可读性和可维护性。

  • 出于一些技术原因,最好是只使用async/await。

  • 借助Visual Studio Code或其他IDE,你可以轻松地迁移现有的promise链代码!


英文原文:https://blog.logrocket.com/promise-chaining-is-dead-long-live-async-await-445897870abc


2019 年 1 月 02 日 10:209614
用户头像

发布了 731 篇内容, 共 359.6 次阅读, 收获喜欢 1824 次。

关注

评论 9 条评论

发布
用户头像
不错 打算后面代码全部用async代替promise了
2020 年 12 月 26 日 11:02
回复
用户头像
但是有时候需要并行执行几个await函数,还是需要用到Promise.al()吧
2019 年 01 月 02 日 17:11
回复
同意你的观点:
var n = await x()
var m = await y()

跟promise.all(x(), y()) 是完全不一样的,前者是串联,后者是并联
2019 年 01 月 03 日 04:25
回复
略微纠正一下,是await promise.all([x(),y()])
2019 年 01 月 03 日 10:04
回复
这位老铁似乎没有认真看完文章就发表评论了?我认为文章中已经给出了一个很好的例子作为想法阐述了(个人而言也很喜欢这个想法),就是把await的位子移动一下…简单举例比如你需要fetch两个文件来分别展示,那么在定义两个fetch行为变量前都不需要加await,只要在使用fetch结果的时候加await。因为fetch这个行为是在定义变量时已经进行的所以也可以说是等同于并行异步了。
然后我这里的想法是这个写法一定程度上添加了思考负担,await语法比迭代器更被人接受就是因为它出色的易读性,只要看到await就明白代码会停下,也会思考这里触发一个异步行为;采用了上述写法后产生的一个问题就是我在看到的await与异步行为并不挂钩:我可能有上百个await,但是实际上异步只进行了一次的。我使用Promise.all进行一次数组解构后得到一个普通变量使用,岂不美哉?
然后下一个,我认为promise的意义更多地应该体现于 同步/异步 的转换,他的魅力一方面来源于对同步语句的异步化(这个说话稍显水货但我一时间想不到什么好的用词,其实也就是promise化),我有可能在等一个事件来了我再执行下一步逻辑,这个时候就是用promise来包裹一下(当然,注意文章抨击的promise链不是promise本体);然后另一方面我认为promise.all/race还是有存在意义的,他能以更高的可读性表达一个逻辑的竞态与依赖,上述的写法把await的地点分散之后,假如我想知道什么时候这个异步是保证完成了的,那么我可能得慢慢找,它的第一次await在哪里,而Promise.all就不一样,你只需要这个变量在何处被引用过,引用他的就是Promise.all的地方,也是异步动作终止的地方。
展开
2019 年 01 月 05 日 22:47
回复
查看更多回复
用户头像
看明白了!
2019 年 01 月 02 日 11:46
回复
没有更多评论了
发现更多内容

据说99.99%的人都会答错的类加载的问题

AI乔治

Java 架构 JVM 类加载 性能调优

手把手带你玩转 openEuler | openEuler 的使用

openEuler

操作系统 openEuler

架构师第一期作业(第5周)

Cheer

作业

目标2025:通信产业在能源变局中拥抱智能未来

脑极体

Servlet-技术专题-Servlet3异步原理与实践

李浩宇/Alex

java安全编码指南之:ThreadPool的使用

程序那些事

java安全编码 java编码指南 java安全编码指南 java代码规范

APP 莫名崩溃,开始以为是 Header 中 name 大小写的锅,最后发现原来是容器的错!

程序员小航

Java bug Header携带签名 工作笔记 问题排查

十八、深入Python函数

刘润森

Python

关注你自己,如同篮球巨星一样,让身体最佳化,持续投入最爱的事情。

叶小鍵

健康 科普 王立铭 肥胖

速度(Velocity)不背这个锅

BY林子

敏捷开发 估算与计划

spring-boot-route(二十)Spring Task实现简单定时任务

Java旅途

Java Spring Boot Spring Task

MySQL-技术专题-联合索引最左前缀匹配原则

李浩宇/Alex

go-zero 如何应对海量定时/延迟任务?

Kevin Wan

golang 定时任务 时间轮 microservice 延迟任务

在算力“沃土”上,种植互联网下一个奇迹十年

脑极体

微服务架构:基于微服务和Docker容器技术的PaaS云平台架构设计(微服务架构实施原理)

AI乔治

Java 架构 微服务 ,docker

最新版MySQL在MacOS上的安装与使用

王磊

MySQL

数字货币永续合约平台搭建方案,一键跟单系统开发

WX13823153201

iOS底层原理之—dyld与objc的关联

iOSer

ios开发 iOS Developer dyld objc

sync-player:使用websocket实现异地同步播放视频

GoEasy消息推送

websocket 数据同步 实时通信

10个自动化测试框架,测试工程师用起来

华为云开发者社区

软件 测试 质量

PLSQL 过程语言-结构化查询语言

Flychen

黄金圈法则:成功者必备的深度思考方法

陆陆通通

黄金圈法则 厉害 牛逼

MySQL-技术专题-聚集索引和慢查询

李浩宇/Alex

深度详解企业CRM系统,体验软件快速开发平台

Marilyn

敏捷开发 快速开发 CRM

忘记MySQL密码怎么办?一招教你搞定!

王磊

MySQL

Java Reference核心原理分析

AI乔治

Java 架构 JVM 性能调优

帆软授权失效处理

Flychen

计算机网络基础知识总结

cxuan

计算机网络 计算机

LAXCUS大数据集群操作系统:一个分布式分时共享E级系统软件(四)

陈泽云

人工智能 大数据 数据结构 操作系统 数据存储

云原生在京东丨基于 Tekton 打造下一代云原生 CI 平台

京东智联云开发者

ci 云原生 Tekton

金九银十期间成功斩获58万架构师Offer!六面字节跳动面经和面试题分享

Java架构追梦

Java 学习 架构 面试 JVM

有了async/await,你可以丢掉promise链了-InfoQ