热门标签 | 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


推荐阅读
  • 我的读书清单(持续更新)201705311.《一千零一夜》2006(四五年级)2.《中华上下五千年》2008(初一)3.《鲁滨孙漂流记》2008(初二)4.《钢铁是怎样炼成的》20 ... [详细]
  • 理解浏览器历史记录(2)hashchange、pushState
    阅读目录1.hashchange2.pushState本文也是一篇基础文章。继上文之后,本打算去研究pushState,偶然在一些信息中发现了锚点变 ... [详细]
  • Windows操作系统提供了Encrypting File System (EFS)作为内置的数据加密工具,特别适用于对NTFS分区上的文件和文件夹进行加密处理。本文将详细介绍如何使用EFS加密文件夹,以及加密过程中的注意事项。 ... [详细]
  • 本文详细介绍了如何利用 Bootstrap Table 实现数据展示与操作,包括数据加载、表格配置及前后端交互等关键步骤。 ... [详细]
  • 深入解析WebP图片格式及其应用
    随着互联网技术的发展,无论是PC端还是移动端,图片数据流量占据了很大比重。尤其在高分辨率屏幕普及的背景下,如何在保证图片质量的同时减少文件大小,成为了亟待解决的问题。本文将详细介绍Google推出的WebP图片格式,探讨其在实际项目中的应用及优化策略。 ... [详细]
  • 本文详细探讨了在Web开发中常见的UTF-8编码问题及其解决方案,包括HTML页面、PHP脚本、MySQL数据库以及JavaScript和Flash应用中的乱码问题。 ... [详细]
  • 高效的JavaScript异步资源加载解决方案
    本文探讨了如何通过异步加载技术处理网页中大型第三方插件的加载问题,避免将大文件打包进主JS文件中导致的加载时间过长,介绍了实现异步加载的具体方法及其优化。 ... [详细]
  • 微信小程序开发指南:创建动态电影选座界面
    本文详细介绍如何在微信小程序中实现一个动态且可视化的电影选座组件,提高用户体验。通过合理的布局和交互设计,使用户能够轻松选择心仪的座位。 ... [详细]
  • JavaScript 实现图片文件转Base64编码的方法
    本文详细介绍了如何使用JavaScript将用户通过文件输入控件选择的图片文件转换为Base64编码字符串,适用于Web前端开发中图片上传前的预处理。 ... [详细]
  • 使用TabActivity实现Android顶部选项卡功能
    本文介绍如何通过继承TabActivity来创建Android应用中的顶部选项卡。通过简单的步骤,您可以轻松地添加多个选项卡,并实现基本的界面切换功能。 ... [详细]
  • MySQL InnoDB 存储引擎索引机制详解
    本文深入探讨了MySQL InnoDB存储引擎中的索引技术,包括索引的基本概念、数据结构与算法、B+树的特性及其在数据库中的应用,以及索引优化策略。 ... [详细]
  • Bootstrap Paginator 分页插件详解与应用
    本文深入探讨了Bootstrap Paginator这款流行的JavaScript分页插件,提供了详细的使用指南和示例代码,旨在帮助开发者更好地理解和利用该工具进行高效的数据展示。 ... [详细]
  • 实践指南:使用Express、Create React App与MongoDB搭建React开发环境
    本文详细介绍了如何利用Express、Create React App和MongoDB构建一个高效的React应用开发环境,旨在为开发者提供一套完整的解决方案,包括环境搭建、数据模拟及前后端交互。 ... [详细]
  • HTML前端开发:UINavigationController与页面间数据传递详解
    本文详细介绍了如何在HTML前端开发中利用UINavigationController进行页面管理和数据传递,适合初学者和有一定基础的开发者学习。 ... [详细]
  • 使用JavaScript实现Tab栏切换功能
    本文将详细介绍如何利用JavaScript实现一个动态的Tab栏切换效果。通过具体的代码示例,帮助读者理解并掌握这一前端技术的应用。 ... [详细]
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社区 版权所有