明明有了 promise ,为啥还需要 async await ?
作者 | Angus安格斯
来源 | https://juejin.cn/post/6960855679208783903
为了让还没听说过这个特性的小伙伴们有一个大致了解,以下是一些关于该特性的简要介绍:
async/await是一种编写异步代码的新方法。在这之前编写异步代码使用的是回调函数和promise。 async/await实际是建立在promise之上的。因此你不能把它和回调函数搭配使用。 async/await可以使异步代码在形式上更接近于同步代码。这就是它最大的价值。
语法
假设有一个getJSON方法,它返回一个promise,该promise会被resolve为一个JSON对象。我们想要调用该方法,输出得到的JSON对象,最后返回"done"。
以下是使用promise的实现方式:
const makeRequest = () =>getJSON().then(data => {console.log(data)return "done"})
makeRequest()
使用async/await则是这样的:
const makeRequest = async () => {console.log(await getJSON())return "done"
}makeRequest()
使用async/await时有以下几个区别:
在定义函数时我们使用了async关键字。await关键字只能在使用async定义的函数的内部使用。所有async函数都会返回一个promise,该promise最终resolve的值就是你在函数中return的内容。 由于第一点中的原因,你不能在顶级作用域中await一个函数。因为顶级作用域不是一个async方法。
// this will not work in top level
// await makeRequest()// this will work
makeRequest().then((result) => {// do something
})
await getJSON()意味着直到getJSON()返回的promise在resolve之后,console.log才会执行并输出resolove的值。
为何使用async/await编写出来的代码更好呢?
1. 简洁
看看我们节省了多少代码吧。即使是在这么一个简单的例子中,我们也节省了可观的代码。我们不需要为.then编写一个匿名函数来处理返回结果,也不需要创建一个data变量来保存我们实际用不到的值。我们还避免了代码嵌套。这些小优点会在真实项目中变得更加明显。
2. 错误处理
async/await终于使得用同一种构造(古老而好用的try/catch) 处理同步和异步错误成为可能。在下面这段使用promise的代码中,try/catch不能捕获JSON.parse抛出的异常,因为该操作是在promise中进行的。要处理JSON.parse抛出的异常,你需要在promise上调用.catch并重复一遍异常处理的逻辑。通常在生产环境中异常处理逻辑都远比console.log要复杂,因此这会导致大量的冗余代码。
const makeRequest = () => {try {getJSON().then(result => {// this parse may failconst data = JSON.parse(result)console.log(data)})// uncomment this block to handle asynchronous errors// .catch((err) => {// console.log(err)// })} catch (err) {console.log(err)}
}
现在看看使用了async/await的情况,catch代码块现在可以捕获JSON.parse抛出的异常了:
const makeRequest = async () => {try {// this parse may failconst data = JSON.parse(await getJSON())console.log(data)} catch (err) {console.log(err)}
}
3. 条件分支
假设有如下逻辑的代码。请求数据,然后根据返回数据中的某些内容决定是直接返回这些数据还是继续请求更多数据:
const makeRequest = () => {return getJSON().then(data => {if (data.needsAnotherRequest) {return makeAnotherRequest(data).then(moreData => {console.log(moreData)return moreData})} else {console.log(data)return data}})
}
只是阅读这些代码已经够让你头疼的了。一不小心你就会迷失在这些嵌套(6层),空格,返回语句中。(当然我们一般用请求数据的返回值作为判断条件不会写成这样,也许我这个小白会...) 在使用async/await改写后,这段代码的可读性大大提高了:
const makeRequest = async () => {const data = await getJSON()if (data.needsAnotherRequest) {const moreData = await makeAnotherRequest(data);console.log(moreData)return moreData} else {console.log(data)return data }
}
4. 中间值
比如你向一个url1发送请求,拿到返回值1,然后用这个返回值1当作参数去请求url2,拿到返回值2,然后拿返回值1和返回值2作为参数去请求url3,拿到最终的返回结果。 对应的代码看起来是这样的:
const makeRequest = () => {return promise1().then(value1 => {// do somethingreturn promise2(value1).then(value2 => {// do something return promise3(value1, value2)})})
}
如果promise3没有用到value1,那么我们就可以把这几个promise改成嵌套的模式。如果你不喜欢这种编码方式,你也可以把value1和value2封装在一个Promsie.all调用中以避免深层次的嵌套:
const makeRequest = () => {return promise1().then(value1 => {// do somethingreturn Promise.all([value1, promise2(value1)])}).then(([value1, value2]) => {// do something return promise3(value1, value2)})
}
这种方式为了保证可读性而牺牲了语义。除了避免嵌套的promise,没有其它理由要把value1和value2放到一个数组里。
同样的逻辑如果换用async/await编写就会非常简单,直观。
const makeRequest = async () => {const value1 = await promise1()const value2 = await promise2(value1)return promise3(value1, value2)
}
5. 异常堆栈
假设有一段串行调用多个promise的代码,在promise串中的某一点抛出了异常:
const makeRequest = () => {return callAPromise().then(() => callAPromise()).then(() => callAPromise()).then(() => callAPromise()).then(() => callAPromise()).then(() => {throw new Error("oops");})
}makeRequest().catch(err => {console.log(err);// output// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)})
从promise串返回的异常堆栈中没有包含关于异常是从哪一个环节抛出的信息。更糟糕的是,它还会误导你,它包含的唯一的函数名是callAPromise,然而该函数与此异常并无关系。(这种情况下文件名和行号还是有参考价值的)。
然而,在使用了async/await的代码中,异常堆栈指向了正确的函数:
const makeRequest = async () => {await callAPromise()await callAPromise()await callAPromise()await callAPromise()await callAPromise()throw new Error("oops");
}makeRequest().catch(err => {console.log(err);// output// Error: oops at makeRequest (index.js:7:9)})
这带来的好处在本地开发环境中可能并不明显,但当你想要在生产环境的服务器上获取有意义的异常信息时,这会非常有用。在这种情况下,知道异常来自makeRequest而不是一连串的then调用会有意义的多。
6. 调试
最后压轴的一点,使用async/await最大的优势在于它很容易被调试。由于以下两个原因,调试promise一直以来都是很痛苦的。
你不能在一个返回表达式的箭头函数中设置断点(因为没有代码块)
如果你在一个.then代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的.then代码块,因为调试器只能跟踪同步代码的『每一步』。
通过使用async/await,你不必再使用箭头函数。你可以对await语句执行步进操作,就好像他们都是普通的同步调用一样。
结论 async/await是过去几年中JavaScript引入的最具革命性的特性之一。它使你意识到promise在语法上的糟糕之处,并提供了一种简单,直接的替代方案。
参考文章
https://loveky.github.io/2017/04/09/translte-6-reasons-why-javascripts-async-await-blows-promises-away/
往期推荐
重装IDEA再也不愁了,一招搞定同步个人配置!
用办公电脑存不雅视频,结果被告了...
小米宣布发放价值15.3亿元的股票!人均39万,最小授予者仅24岁
为什么catch了异常,但事务还是回滚了?
骚操作!阿里云直接买www.huaweicloud.com的关键词来抢生意?
喜欢本文欢迎转发,关注我订阅更多精彩
关注我回复「加群」,加入Spring技术交流群
明明有了 promise ,为啥还需要 async await ?相关推荐
- ES Next Arrow function Promise Iterator Generator yield Async Await
ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await It ...
- 让IE9及以上兼容es6,Promise, 及es7的async await
在完成下面A和B两步后,页面内嵌JS或者引入自己外部JS,script标签的type属性需要设置为text/babel <!DOCTYPE html> <html><he ...
- 03-es6语法 Promise 和 es8语法 async await 的了解和基本使用
1 //Promise 2 3 // 1.说明 : Promise 是 es6 中提出的新语法 4 // 2.作用 : 用 '用户编写同步代码' 的方式 '处理异步' 的一种解决方案 5 // 3.以 ...
- promise 和 async await区别
什么是Async/Await? async/await是写异步代码的新方式,以前的方法有回调函数和Promise. async/await是基于Promise实现的,它不能用于普通的回调函数. as ...
- angular2 学习笔记 ( Rxjs, Promise, Async/Await 的区别 )
Promise 是 ES 6 Async/Await 是 ES 7 Rxjs 是一个 js 库 在使用 angular 时,你会经常看见这 3 个东西. 它们都和异步编程有关,有些情况下你会觉得用它们 ...
- async function_掌握 Async/Await
摘要: 还不用Async/Await就OUT了.. 原文:掌握 Async/Await 作者:Jartto Fundebug经授权转载,版权归原作者所有. 前端工程师肯定都经历过 JS 回调链狱的痛苦 ...
- 【JS】1015- 异步编程的终极解决方案 async/await
早期的回调函数 回调函数我们经常有写到,比如: ajax(url, (res) => {console.log(res); }) 复制代码 但是这种回调函数有一个大缺陷,就是会写出 回调地狱(C ...
- 在微信小程序中使用 async/await
微信小程序中有大量接口是异步调用,比如 wx.login() . wx.request() . wx.getUserInfo() 等,都是使用一个对象作为参数,并定义了 success() . fai ...
- async/await的基础用法
一.async/await的优点 1)方便级联调用:即调用依次发生的场景: 2)同步代码编写方式: Promise使用then函数进行链式调用,一直点点点,是一种从左向右的横向写法:async/awa ...
最新文章
- 题目1051:数字阶梯求和
- Codeforces 516D Drazil and Morning Exercise (栈、二分)
- Matlab 图像的邻域和块操作
- 狂神说es笔记_人教版七上英语Unit5电子课本音频+课堂笔记+课后同步习题
- Linux 阵列卡驱动安装
- 问题与事务跟踪系统jira中的版本管理
- springboot项目发布JAR包
- HttpInvoker-----客户端实现
- selenium实现chrome分屏截图的合并
- 我的博客园开张了,记录每天学习,工作。
- log4cpp 用法
- Katalon Recorder 自动录制 Selenium 爬虫脚本
- 远程协同网络架构photon cloud
- 网易公开课 mysql_“网易云课程”SQL分析
- 跟我一起学Linux系统编程006C-进程内存分配,堆分配brk、malloc、free
- C#一个解决方案创建多个项目
- 【专家视点】公域流量的尽头:数字营销回归商业本质(20页精品PPT下载)
- 理想电压源和理想电流源
- 注意力机制——Spatial Transformer Networks(STN)
- 【arduino】arduino家族,arduino相关各种开发环境汇总,Mixly米思齐最新python开发环境...
热门文章
- 利用Eclipse开发Linux驱动
- Cowboy 源码分析(一)
- Revit二次开发之“遍历材质判断材质类别的新方法”BuiltInParameter.PHY_MATERIAL_PARAM_CLASS...
- 某系统响应时间慢TPS低性能瓶颈调优过程
- docker容器 与 系统时间同步
- activemq ActiveMQ 两个默认端口 8161和61616的区别
- linux shell trap捕捉信号 附信号表 SIGTERM SIGKILL
- linux docker中gdb调试断点不停
- linux c 复制拷贝文件
- Atheros无线网卡芯片全介绍