热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[翻译]Async/Await使你的代码更简约

写在文章前这篇文章翻译自ASYNCAWAITWILLMAKEYOURCODESIMPLER,这是一篇写于2017年八月的文章,并由某专栏提名为17年十大必读文章。翻译的不好的处所,

写在文章前

这篇文章翻译自 ASYNC/AWAIT WILL MAKE YOUR CODE SIMPLER,这是一篇写于2017年八月的文章,并由某专栏提名为17年十大必读文章。翻译的不好的处所,还望人人指出, ̄▽ ̄ 感谢。

或者说,我怎样进修不运用回调函数而且爱上ES8

偶然,当代Javascript项目会离开我们的掌控。个中一个重要的罪魁祸首就是芜杂的处置惩罚异步的使命,致使写出了又长又庞杂又深层嵌套的代码块。Javascript现在供应了一个新的处置惩罚这些操纵的语法,他以至能把最扑朔迷离的操纵转化成为简约而且可读性高的代码

背景

AJAX (Asynchronous Javascript And XML)

首先来举行一点科普。 在90年代末期, Ajax是异步Javascript的第一个严重突破。 这个手艺可以让网站在html加载以后猎取和展现新的数据。关于当时大部分网站的那种需要从新下载全部个页面来展现一个部分内容的更新来讲,它是革命性的立异。这项手艺(在jQuery中经由历程绑缚成为辅佐函数而著名)在全部21天下主导了web开辟,同时ajax在本日也是网站用来检索数据的重要手艺,但xml却被json大规模的庖代

NodeJS

当NodeJS在2009年第一次宣布的时刻,服务端的一个重要的关注点就是许可递次文雅的处置惩罚并发。当时大部分的服务端言语运用壅塞代码完成的这类体式格局来处置惩罚I/O操纵,直到它完毕处置惩罚I/O操纵以后再继承举行之前的代码运转。取而代之,NodeJS运用事宜轮回系统,运用了一种相似ajax语法的工作体式格局:一旦非壅塞的异步操纵完成以后,就可以让开辟者分派的回调函数被触发。

Promises

几年以后,一个新的叫做“promises”的规范出现在nodejs和浏览器环境中,他供应了一套更壮大也更规范化的体式格局去构建异步操纵。promises 照旧运用基于回调的花样,然则为异步操纵的链式挪用和构建供应了一致的语法。promises,这类由盛行的开源库所制造的规范,终究在2015年被到场了原生Javascript。

promises虽然是一个严重的革新,但照旧会在某些情况下发生冗杂难读的代码。

现在,我们有了一个新的处理计划。

async/await 是一种许可我们像构建没有回调函数的平常函数一样构建promises的新语法(从 .net和c#自创而来)。 这个是一个极好的Javascript的增添功用,在客岁被加进了Javascript ES7,它以至可以用来简化险些一切现存的js运用。

Examples

我们将会举几个例子。

这些代码例子不需要加载任何的三方库。
Async/await 已在在最新版本的chrome,Firefox,Safari,和edge 取得周全支撑,所以你可以在浏览器的掌握台中试着运转这些示例。别的,async/await 语法可以在Node的7.6版本及其以上运转, Babel 以及TypeScript 也一样支撑async/await 语法。Async和await 现在完整可以在任何Javascript项目中运用

Setup

假如你想在你的电脑上追随我们的脚步探访async,我们就将会运用这个假造的API Class。这个类经由历程返回promise对象来模仿收集的挪用的历程,而且这些promise对象将会在被挪用的200ms以后运用resolve函数将简朴的数据作为参数通报出去。

class Api {
constructor () {
this.user = { id: 1, name: 'test' }
this.friends = [ this.user, this.user, this.user ]
this.photo = 'not a real photo'
}
getUser () {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(this.user), 200)
})
}
getFriends (userId) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(this.friends.slice()), 200)
})
}
getPhoto (userId) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(this.photo), 200)
})
}
throwError () {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Intentional Error')), 200)
})
}
}

每一个例子将会按递次实行雷同的三个操纵:检索一个用户,检索他们的朋侪,以及检索他们的照片。末了,我们将在掌握台输出上述的三个效果。

第一个尝试-嵌套的promise回调函数

下面是运用嵌套的promise回调函数的完成要领

function callbackHell () {
const api = new Api()
let user, friends
api.getUser().then(function (returnedUser) {
user = returnedUser
api.getFriends(user.id).then(function (returnedFriends) {
friends = returnedFriends
api.getPhoto(user.id).then(function (photo) {
console.log('callbackHell', { user, friends, photo })
})
})
})
}

这能够关于任何Javascript运用者来讲再熟习不过了。这个代码块有着非常简朴的目标,而且很长而且高层级嵌套,还以一大群的括号末端

})
})
})
}

在实在的代码库中,每一个回调函数都能够会相称长,这能够会致使发生一些非常冗杂而且高层级嵌套的函数。我们平常管这类在回调的回调中运用回调的代码叫“回调地狱”

更蹩脚的是,没有办法举行毛病搜检,所以任何一个回调都能够会作为一个未处置惩罚的Promise rejection 而激发不轻易发觉的地失利。

第二个尝试 – 链式promise

让我们看看我们是不是是能革新一下

function promiseChain () {
const api = new Api()
let user, friends
api.getUser()
.then((returnedUser) => {
user = returnedUser
return api.getFriends(user.id)
})
.then((returnedFriends) => {
friends = returnedFriends
return api.getPhoto(user.id)
})
.then((photo) => {
console.log('promiseChain', { user, friends, photo })
})
}

promise的一个很好的特征就是他们可以经由历程在每一个回调内部返回别的一个promise对象而举行链式操纵。这个要领可以将一切的回调视作为平级的。别的,我们还可以运用箭头函数来缩写回调的表达式。

这个变体显著比之前的谁人尝试更易读,而且另有很好的序列感。但是,很遗憾,照旧很冗杂,看起来另有点庞杂

第三个尝试 Async/Await

有无能够我们不运用任何的回调函数?不能够吗?有想过只用7行就完成它的能够性吗?

async function asyncAwaitIsYourNewBestFriend () {
const api = new Api()
const user = await api.getUser()
const friends = await api.getFriends(user.id)
const photo = await api.getPhoto(user.id)
console.log('asyncAwaitIsYourNewBestFriend', { user, friends, photo })
}

变得更好了有无?在promise之前挪用await暂停了函数流直到promise 处于resolved状况,然后将效果赋值给等号左侧的变量。这个体式格局能让我们编写一个就像是一个一般的同步敕令一样的异步操纵流程。

我想你现在和我一样,对这个特征觉得非常的冲动有无?!

注重“async”关键词是在全部函数声明的最先声明的。我们必需要这么做,由于实在它将全部函数转化成为一个promise。我们将会在稍后研讨它。

LOOPS(轮回)

Async/await让之前的非常庞杂的操纵变得迥殊简朴,比如说, 到场我们想按递次取回每一个用户的朋侪列表该怎么办?

第一个尝试 – 递归的promise轮回

下面是怎样根据递次猎取每一个朋侪列表的体式格局,这能够看起来很像很平常的promise。

function promiseLoops () {
const api = new Api()
api.getUser()
.then((user) => {
return api.getFriends(user.id)
})
.then((returnedFriends) => {
const getFriendsOfFriends = (friends) => {
if (friends.length > 0) {
let friend = friends.pop()
return api.getFriends(friend.id)
.then((moreFriends) => {
console.log('promiseLoops', moreFriends)
return getFriendsOfFriends(friends)
})
}
}
return getFriendsOfFriends(returnedFriends)
})
}

我们创建了一个内部函数用来经由历程回调链式的promises猎取朋侪的朋侪,直到列表为空。O__O 我们确实完成了功用,很棒棒,然则我们实在运用了一个非常庞杂的计划来处理一个相称简朴的使命。

注重 – 运用
promise.all()来尝试简化
PromiseLoops()函数会致使它表现为一个有着完整差别的功用的函数。这个代码段的目标是按递次(一个接着一个)运转操纵,但
Promise.all是同时运转一切异步操纵(一次性运转一切)。然则,值得强调的是, Async/await 与
Promise.all()连系运用照旧非常的壮大,就像我们下一个小节所展现的那样。

第二次尝试- Async/Await的for轮回

这个能够就非常的简朴了。

async function asyncAwaitLoops () {
const api = new Api()
const user = await api.getUser()
const friends = await api.getFriends(user.id)
for (let friend of friends) {
let moreFriends = await api.getFriends(friend.id)
console.log('asyncAwaitLoops', moreFriends)
}
}

不需要写任何的递归Promise,只要一个for轮回。看到了吧,这就是你的人生良朋-Async/Await

PARALLEL OPERATIONS(并行操纵)

逐一猎取每一个朋侪列表好像有点慢,为何不采纳并行实行呢?我们可以运用async/await 来完成这个需求吗?

明显,可以的。你的朋侪它可以处理任何题目。:)

async function asyncAwaitLoopsParallel () {
const api = new Api()
const user = await api.getUser()
const friends = await api.getFriends(user.id)
const friendPromises = friends.map(friend => api.getFriends(friend.id))
const moreFriends = await Promise.all(friendPromises)
console.log('asyncAwaitLoopsParallel', moreFriends)
}

为了并行的运转这些操纵,要先天生成运转的promise数组,并把它作为一个参数传给Promise.all()。它返回给我们一个唯一的promise对象可以让我们举行await, 这个promise对象一旦一切的操纵都完成了就将会变成resolved状况。

Error handling (毛病处置惩罚)

但是,这篇文章到目前为止还没有说到谁人异步编程的重要题目:毛病处置惩罚。 很多代码库的灾害泉源就在于异步的毛病处置惩罚一般涉及到为每一个操纵写零丁的毛病处置惩罚的回调。由于将毛病放到挪用客栈的顶部会很庞杂,而且一般需要在每一个回调的最先明白搜检是不是有毛病抛出。这类要领是非常烦琐冗杂而且轻易失足的。何况,在一个promise中抛出的任何非常假如没有被准确捕捉的话,都邑发生一个不被发觉的失利,从而致使代码库有由于不完整毛病磨练而发生的“不可见毛病”。

让我们从新回到之前的例子中给每一种尝试增加毛病处置惩罚。我们将在猎取用户图片之前运用一个分外的函数api.throwError()来检测毛病处置惩罚。

第一个尝试 – promise的毛病回调函数

让我们来看看最蹩脚的写法:

function callbackErrorHell () {
const api = new Api()
let user, friends
api.getUser().then(function (returnedUser) {
user = returnedUser
api.getFriends(user.id).then(function (returnedFriends) {
friends = returnedFriends
api.throwError().then(function () {
console.log('Error was not thrown')
api.getPhoto(user.id).then(function (photo) {
console.log('callbackErrorHell', { user, friends, photo })
}, function (err) {
console.error(err)
})
}, function (err) {
console.error(err)
})
}, function (err) {
console.error(err)
})
}, function (err) {
console.error(err)
})
}

太恶心了。除了真的很长很丑这个瑕玷以外,掌握流也是非常不直观,由于他是从外层进入,而不是像一般的可读性高的代码一样那种是由上至下的。太蹩脚了,我们继承第二个尝试。

第二个尝试- 链式promise捕捉要领

我们可以经由历程运用一种promise-catch组合(先promise再捕捉再promise再再捕捉)的体式格局来革新一下。

function callbackErrorPromiseChain () {
const api = new Api()
let user, friends
api.getUser()
.then((returnedUser) => {
user = returnedUser
return api.getFriends(user.id)
})
.then((returnedFriends) => {
friends = returnedFriends
return api.throwError()
})
.then(() => {
console.log('Error was not thrown')
return api.getPhoto(user.id)
})
.then((photo) => {
console.log('callbackErrorPromiseChain', { user, friends, photo })
})
.catch((err) => {
console.error(err)
})
}

明显比之前的好太多,经由历程运用链式promise的末了的谁人单个的catch函数,我们可认为一切的操纵供应单个毛病处置惩罚。然则,照旧有点庞杂,我们照样必需要运用特别的回调函数来处置惩罚异步毛病,而不是像处置惩罚平常的Javascript毛病一样处置惩罚异步毛病。

第三个尝试-一般的try/catch块

我们可以做的更好。

async function aysncAwaitTryCatch () {
try {
const api = new Api()
const user = await api.getUser()
const friends = await api.getFriends(user.id)
await api.throwError()
console.log('Error was not thrown')
const photo = await api.getPhoto(user.id)
console.log('async/await', { user, friends, photo })
} catch (err) {
console.error(err)
}
}

这里,我们将全部操纵封装在一个一般的try/catch 块中。如许的话,我们就可以运用一样的体式格局从同步代码和一步代码中抛出并捕捉毛病。明显,简朴的多;)

Composition(组合)

我在之前提到说,任何带上async 标签的函数实际上返回了一个promise对象。这可以让我们组合异步掌握流变得非常的简朴。

比如说,我们可以从新配置之前的那些例子来返回用户数据而不是输出它,然后我们可以经由历程挪用async函数作为一个promise对象来检索数据。

async function getUserInfo () {
const api = new Api()
const user = await api.getUser()
const friends = await api.getFriends(user.id)
const photo = await api.getPhoto(user.id)
return { user, friends, photo }
}
function promiseUserInfo () {
getUserInfo().then(({ user, friends, photo }) => {
console.log('promiseUserInfo', { user, friends, photo })
})
}

更好的是,我们也可以在吸收的函数中运用async/await语法,从而天生一个完整清楚易懂,以至很精华精辟的异步编程代码块。

async function awaitUserInfo () {
const { user, friends, photo } = await getUserInfo()
console.log('awaitUserInfo', { user, friends, photo })
}

假如我们现在需要检索前十个用户的一切数据呢?

async function getLotsOfUserData () {
const users = []
while (users.length <10) {
users.push(await getUserInfo())
}
console.log('getLotsOfUserData', users)
}

请求并发的情况下呢?还要有严谨的毛病处置惩罚呢?

async function getLotsOfUserDataFaster () {
try {
const userPromises = Array(10).fill(getUserInfo())
const users = await Promise.all(userPromises)
console.log('getLotsOfUserDataFaster', users)
} catch (err) {
console.error(err)
}
}

Conclusion(结论)

跟着单页Javascript web递次的鼓起和对NodeJS的普遍采纳,怎样文雅的处置惩罚并发关于Javascript开辟人员来讲比任何以往的时刻都显得更为重要。Async/Await缓解了很多由于掌握流题目而致使bug各处的这个搅扰着Javascript代码库数十年的题目,而且险些可以保证让任何异步代码块变的更精华精辟,更简朴,更自信。而且近期async/await 已在险些一切的主流浏览器以及nodejs上面取得周全支撑,因而现在恰是将这些手艺集成到本身的代码实践以及项目中的最好机遇。

议论时候

到场到reddit的议论中

async/await让你的代码更简朴1

async/await让你的代码更简朴2


推荐阅读
  • Microsoft即将发布WPF/E的CTP(Community Technology Preview)和SDK,标志着RIA(Rich Internet Application)技术的新里程碑。更多详情及下载链接请参见MSDN官方页面。 ... [详细]
  • 本文探讨了如何通过WebBrowser控件在用户点击输入框时自动显示图片验证码。该过程可能涉及JavaScript事件的触发与响应。 ... [详细]
  • 远程过程调用(RPC)是一种允许客户端通过网络请求服务器执行特定功能的技术。它简化了分布式系统的交互,使开发者可以像调用本地函数一样调用远程服务,并获得返回结果。本文将深入探讨RPC的工作原理、发展历程及其在现代技术中的应用。 ... [详细]
  • 本文探讨了浏览器的同源策略限制及其对 AJAX 请求的影响,并详细介绍了如何在 Spring Boot 应用中优雅地处理跨域请求,特别是当请求包含自定义 Headers 时的解决方案。 ... [详细]
  • ServletContext接口在Java Web开发中扮演着重要角色,它提供了一种方式来获取关于整个Web应用程序的信息。通过ServletContext,开发者可以访问初始化参数、共享数据以及应用资源。 ... [详细]
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
  • 简化报表生成:EasyReport工具的全面解析
    本文详细介绍了EasyReport,一个易于使用的开源Web报表工具。该工具支持Hadoop、HBase及多种关系型数据库,能够将SQL查询结果转换为HTML表格,并提供Excel导出、图表显示和表头冻结等功能。 ... [详细]
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
  • springMVC JRS303验证 ... [详细]
  • 本文将详细介绍通过CAS(Central Authentication Service)实现单点登录的原理和步骤。CAS由耶鲁大学开发,旨在为多应用系统提供统一的身份认证服务。文中不仅涵盖了CAS的基本架构,还提供了具体的配置实例,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文将详细介绍如何在ThinkPHP6框架中实现多数据库的部署,包括读写分离的策略,以及如何通过负载均衡和MySQL同步技术优化数据库性能。 ... [详细]
  • 本文深入探讨网页游戏的开发流程,涵盖从程序框架设计到具体实现的技术细节,旨在为开发者提供全面的指导。 ... [详细]
  • 本文详细探讨了JavaScript中的闭包与柯里化技术,这两者是函数式编程的重要组成部分,对提升代码的灵活性和可维护性具有重要作用。 ... [详细]
  • C# 对象转 JSON 字符串的方法与应用
    本文介绍如何在 C# 中使用一般处理程序(ASHX)将对象转换为 JSON 字符串,并通过设置响应类型为 application/json 来确保客户端能够正确解析返回的数据。同时,文章还提供了 HTML 页面中不依赖 jQuery 的 AJAX 方法来接收和处理这些 JSON 数据的具体实现。 ... [详细]
  • ABBYY FineReader:高效PDF转换、精准OCR识别与文档对比工具
    在处理PDF转换和OCR识别时,您是否遇到过格式混乱、识别率低或图表无法正常识别的问题?ABBYY FineReader以其强大的功能和高精度的识别技术,完美解决这些问题,帮助您轻松找到最终版文档。 ... [详细]
author-avatar
阳_光shine
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有