译者按: 在Async/Await替代Promise的6个理由中,我们比较了两种不同的异步编程方法:Async/AwaitPromise,这篇博客将通过示例代码介绍Async/Await是如何简化JavaScript代码的。

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

Async/Await是JavaScript的ES7新特性,来源于.NETC#。它可以不用回调函数,像同步代码那些编写异步代码。这篇博客将通过一些代码示例,来说明Async/Await如何简化JavaScript代码。

1. 去除回调函数

运行本文的示例代码,并不需要额外的函数库。对于最新版的主流浏览器中,例如Chrome,Firefox, Safari以及Edge,它们都支持Async/Await语法。另外,Node.js 7.6+也支持了Async/Await语法。

我们编写了一些简单的API接口,用于模拟异步操作。这些接口都返回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

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 })
})
})
})
}

曾经使用Promise编写回调函数的开发者一定不会陌生,这样一层层的嵌套代码通常是这样结尾的:

      })
})
})
}

链式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 })
})
}

Async/AwaitPromise的最佳特性之一,就是可以在then回调函数中,return一个新的Promise,这样就可以将这些Promise链接起来,只有一层嵌套。链式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 })
}

2. 简化循环

使用await关键词时,赋值操作将等到异步操作结束时才进行。这样,看起来与同步代码无异,实际执行事实上是异步的。

Async/Await可以让一些复杂操作,比如循环变得简单。例如,当我们需要获取某个user的所有friends的friends列表,应该怎样操作呢?

使用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)
})
}

使用Promise.all()来实现的话,则并非循环,而是并发执行。我们使用了递归函数getFriendsOfFriends来获取friends-of-friends,知道friends数组为空。如此简单的任务,这样写显然过于复杂了。

使用Async/Await

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)
}
}

这时,可以直接使用for循环来实现,非常简单。 

3. 简化并发

使用循环逐个获取friends-of-friends显然太慢,采用并发方式更为简单。

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()的参数即可。这样,只需要await一个Promise,而这个Promise会在所有并发操作结束时resolve

4. 简化错误处理

使用回调函数处理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)
})
}

这样做非常糟糕,代码非常冗余,可读性也很差。

使用catch方法处理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方法处理所有错误。

使用Try/Catch处理Async/Await错误

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)
}
}

如何你需要监控线上JavaScript代码的错误时,可以免费使用Fundebug的实时错误监控服务,只需要一行代码就可以搞定!对于Async/Await代码,使用Try/Catch即可处理,和同步代码一样,更加简单。

5. 简化代码组织

使用async关键词定义的函数都会返回Promise,这样可以更方便地组织代码。

例如,在之前的示例中,我们可以将获取的user信息return,而不是直接打印;然后,我们可以通过返回的Promise来获取user信息。

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 })
}

如何获取多个user的信息?

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)
}
}

 

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。
自从2016年双十一正式上线,Fundebug累计处理了9亿+错误事件,得到了Google、360、金山软件等众多知名用户的认可。欢迎免费试用!

版权声明:

转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/10/16/async-await-simplify-javascript/

Async/Await是这样简化JavaScript代码的的更多相关文章

  1. [.NET] 怎样使用 async & await 一步步将同步代码转换为异步编程

    怎样使用 async & await 一步步将同步代码转换为异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6079707.html  ...

  2. 【TypeScript】如何在TypeScript中使用async/await,让你的代码更像C#。

    [TypeScript]如何在TypeScript中使用async/await,让你的代码更像C#. async/await 提到这个东西,大家应该都很熟悉.最出名的可能就是C#中的,但也有其它语言也 ...

  3. [译]async/await中使用阻塞式代码导致死锁 百万数据排序:优化的选择排序(堆排序)

    [译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的 ...

  4. [译]async/await中使用阻塞式代码导致死锁

    原文:[译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Clea ...

  5. 一个真实的Async/Await示例

    译者按: 通过真实的代码示例感受Async/Await的力量. 原文: Async/await - A thorough example 译者: Fundebug 为了保证可读性,本文采用意译而非直译 ...

  6. 重构:从Promise到Async/Await

    摘要: 夸张点说,技术的发展与历史一样,顺之者昌,逆之者亡.JS开发者们,赶紧拥抱Async/Await吧! GitHub仓库: Fundebug/promise-asyncawait 早在半年多之前 ...

  7. JavaScript基础——深入学习async/await

    本文由云+社区发表 本篇文章,小编将和大家一起学习异步编程的未来--async/await,它会打破你对上篇文章Promise的认知,竟然异步代码还能这么写! 但是别太得意,你需要深入理解Promis ...

  8. 现代JS中的流程控制:详解Callbacks 、Promises 、Async/Await

    JavaScript经常声称是_异步_.那是什么意思?它如何影响发展?近年来这种方法有何变化? 请思考以下代码: result1 = doSomething1(); result2 = doSomet ...

  9. Async/Await替代Promise的6个理由

    译者按: Node.js的异步编程方式有效提高了应用性能:然而回调地狱却让人望而生畏,Promise让我们告别回调函数,写出更优雅的异步代码:在实践过程中,却发现Promise并不完美:技术进步是无止 ...

随机推荐

  1. 包建强的培训课程(17):Java代码敏捷之道

    第1讲 千言万语聊注释 按图索骥 奇葩注释“赏析” Git提交的学问 第2讲 RxJava:函数式编程 从一只猫的故事说起 背压 第3讲 代码瘦身 抽象相同逻辑的代码 查找相似代码 AOP一瞥 第4讲 ...

  2. 吴恩达机器学习笔记5-梯度下降I(Gradient descent intuition)

    梯度下降是一个用来求函数最小值的算法,我们将使用梯度下降算法来求出代价函数

  3. CCS3怎么实现border边框渐变效果

    下图注册按钮的边框有渐变效果,如果让你来实现,你会怎么做呢 个人觉得,省事的做法,直接让UI给背景图片就可以了,如下图 不过这种做法感觉不太灵活,如果要修改border的渐变颜色,就需要UI重新做图. ...

  4. SpringMVC框架一:搭建测试

    这里做一个Demo:展示商品列表 新建Dynamic Web Project: 导入jar包,放在lib下: 放入Lib文件夹之后,会自动build path 接下来配置web.xml: <?x ...

  5. SVM算法简单应用

    第一部分:线性可分 通俗解释:可以用一条直线将两类分隔开来 一个简单的例子,直角坐标系中有三个点,A,B点为0类,C点为1类: from sklearn import svm # 三个点 x = [[ ...

  6. Linux基本命令大全

    linux的基本命令 增 mKdir test # 创建一个名为test的文件夹 mkdir -p test1/test2/test3 # 递归创建directory mkdir -p {aaa,bb ...

  7. 【spring】aop切面通知,日志处理

    1.spring的切面编程 概念原理可以看这里:http://blog.csdn.net/moreevan/article/details/11977115 2.所需要的jar包 maven引入jar ...

  8. Kubernetes集群搭建之企业级环境中基于Harbor搭建自己的私有仓库

    搭建背景 企业环境中使用Docker环境,一般出于安全考虑,业务使用的镜像一般不会从第三方公共仓库下载.那么就要引出今天的主题 企业级环境中基于Harbor搭建自己的安全认证仓库 介绍 名称:Harb ...

  9. AutoCloseable的用法

    今天学习JDBC的时候查看了接口Statement的executeQuery()方法的时候偶然发现了一个新的接口: 看到红笔圈出的那句话当时没反应过来,才知道是接口的多继承,但是后面的那个接口并没有见 ...

  10. supervisor 启动dotnet.core 报“ too many start retries too quickly”

    环境: 操作系统:Centos 7 dotnet core:2.0.0    2.1.3 问题: 在使用supervisor 配置守护进程时,启动dotnet.core程序失败,查看/tmp下supe ...