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


推荐阅读
  • JavaScript中字符串转JSON或者XMLJS中经常需要将数据格式从字符串类型转换为JSON或者XML,尤其是string到JSON转换,下面简 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 本文介绍了如何使用JSONObiect和Gson相关方法实现json数据与kotlin对象的相互转换。首先解释了JSON的概念和数据格式,然后详细介绍了相关API,包括JSONObject和Gson的使用方法。接着讲解了如何将json格式的字符串转换为kotlin对象或List,以及如何将kotlin对象转换为json字符串。最后提到了使用Map封装json对象的特殊情况。文章还对JSON和XML进行了比较,指出了JSON的优势和缺点。 ... [详细]
  • 从零基础到精通的前台学习路线
    随着互联网的发展,前台开发工程师成为市场上非常抢手的人才。本文介绍了从零基础到精通前台开发的学习路线,包括学习HTML、CSS、JavaScript等基础知识和常用工具的使用。通过循序渐进的学习,可以掌握前台开发的基本技能,并有能力找到一份月薪8000以上的工作。 ... [详细]
  • 随着前端技术的发展,越来越多的开发者开始使用react、vue等web框架,但很少有人深入理解这些框架的源码。然而,这些框架底层都是由原生的javascript构建而成。对于初学前端的人来说,可能会认为javascript很容易上手,但实际上只是因为它被高度封装了。与能够使用封装类的人相比,能够理解框架原理的人则处于另一个层面。本文将深入剖析jquery源码,探寻框架底层的原理,帮助读者更好地理解web框架的运行机制。 ... [详细]
  • 本文介绍了在满足特定条件时如何在输入字段中使用默认值的方法和相应的代码。当输入字段填充100或更多的金额时,使用50作为默认值;当输入字段填充有-20或更多(负数)时,使用-10作为默认值。文章还提供了相关的JavaScript和Jquery代码,用于动态地根据条件使用默认值。 ... [详细]
  • 前端~javascript~webAPI/文档对象模型Dom/Dom树/事件机制/操作元素/实战案例:实现网页计数器
    文章目录WebAPI简介DomDom树获取Dom元素事件事件三要素操作dom元素innerHTMLinnerText实战案例:实现网页计数器WebAPI简介什么是AP ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • JavaScript设计模式之策略模式(Strategy Pattern)的优势及应用
    本文介绍了JavaScript设计模式之策略模式(Strategy Pattern)的定义和优势,策略模式可以避免代码中的多重判断条件,体现了开放-封闭原则。同时,策略模式的应用可以使系统的算法重复利用,避免复制粘贴。然而,策略模式也会增加策略类的数量,违反最少知识原则,需要了解各种策略类才能更好地应用于业务中。本文还以员工年终奖的计算为例,说明了策略模式的应用场景和实现方式。 ... [详细]
  • 报错现象:从mysql5.5数据库导出的数据结构放到mysql5.7.10报错create_timetimestampNOTNULLDEFAULT‘0000-00-0 ... [详细]
  • 小编给大家分享一下TypeScript2.7有什么改进,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收 ... [详细]
  • 前言小伙伴们大家好。从今天开始我们将从 ... [详细]
  • TS-入门学习笔记TypeScript是JavaScript的一个超集,主要提供了类型系统和对ES6的支持。与js相比,最大的有点是类型系统的引入,由于js本身是弱类型语言,所以天 ... [详细]
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社区 版权所有