理解 async/await
栏目: JavaScript · 发布时间: 5年前
内容简介:现在面对日常工作时,总避免不了面对异步操作带来的一些麻烦。在时代演变的过程中,处理异步的方法有许多种:回调函数、关于async的用法,先看一个简单的小例子:
现在面对日常工作时,总避免不了面对异步操作带来的一些麻烦。在时代演变的过程中,处理异步的方法有许多种:回调函数、 Promise
链式语法、 Generator
函数到现在比较流行的 async
函数。那什么是 async
呢?
async
函数是 Generator
函数的语法糖。使用 async
关键字代替 Generator
函数的星号 *
, await
关键字代替 yield
。相较于Generator函数,async函数改进了以下四点:
-
内置执行器
Generator
函数的执行必须靠执行器,所以才有了co
模块,而async
函数自带执行器。 -
更好的语义
async
和await
,比起*
和yield
,语义更清楚。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。 -
更广的适用性
co
模块约定,yield
命令后面只能是Thunk 函数或Promise 对象,而async
函数的await
命令后面,可以是Promise对象和原始类型的值。 -
返回值是 Promise
async
函数的返回值是Promise对象,这比Generator 函数的返回值是 Iterator对象方便多了。你可以用then
方法指定下一步的操作。
async 用法
关于async的用法,先看一个简单的小例子:
function getProvinces () { return new Promise(resolve => { setTimeout(resolve, 1000) }) } async function asyncFn () { await getProvinces() console.log('hello async') } 复制代码
上面代码先定义了一个获取省份数据的getProvinces函数,其中用setTimeout模拟数据请求的异步操作。当我们在asyncFn 函数前面使用关键字 async
就表明该函数内存在异步操作。当遇到 await
关键字时,会等待异步操作完成后再接着执行接下去的代码。所以代码的执行结果为等待1000毫秒之后才会在控制台中打印出 'hello async'。
了解了 async 的基本用法,接下来理解一下 async 的运作:
async function asyncFn1 () { return 'hello async' } asyncFn1().then(res => { console.log(res) }) // 'hello async' async function asyncFn2 () { throw new Error('error') } asyncFn2().then(res => { console.log(res) }).catch(err => { console.log(err) }) // 'error' 复制代码
async
会返回一个Promise对象,当没发生错误时 return 的值会成为 then
方法回调函数的参数。而当抛出错误时,会导致Promise对象变为 reject
状态,抛出的错误也会成为 catch
方法回调函数的参数。
async function asyncFn3 () { return await Promise.resolve('hello async') } asyncFn3().then(res => console.log(res)) // 'hello async' async function asyncFn4 () { return await 123 } asyncFn3().then(res => console.log(res)) // 123 复制代码
await
(async wait)关键字后面如果是一个Promise对象,则会返回该Promise的结果。如果不是,也会当成立即执行 resolve
,将值返回。
当 async
函数当中存在多个 await
的函数时,我们不得不考虑某个Promise状态变为 reject
的情况,因为只要内部有函数状态改变为 reject
时,接下去的函数将不再执行, async
函数的状态也将变更为 reject
。
async function asyncFn5 () { await Promise.reject('error') return await Promise.resolve('hello async') // 不会执行 } asyncFn5().then(res => { console.log(res) }).catch(err => { console.log(err) }) 复制代码
为了能够正确的执行代码,应该对 await
进行错误处理,基本的错误处理方式有两类:
async function asyncFn6 () { try { await Promise.reject('error') } catch (err) { console.log(err) } return await Promise.resolve('hello async') } // 将可能发生错误的函数使用try...catch进行处理 async function asyncFn7 () { await Promise.reject('error').catch(err => console.log(err)) return await Promise.resolve('hello async') } // 将可能变为reject状态的Promise对象后面跟上一个catch方法,以处理之前发生的错误 复制代码
理解了 async/await
的基本用法,接下来用一个工作中经常会遇到的情景作为例子,感受一下 async/await
的魔力。
假设我们现在要获取一个地级市拥有多少个辖区,我们现在得先调用获取当地省份的接口,从中拿到省份id才能够调用获取地级市的接口,拿到对应地级市的id才能获取最终的结果。
function getProvinces () { ... return new Promise(resolve => { resolve(provinceId) } } function getCitys (provinceId) { ... return new Promise(resolve => { resolve(cityId) } } function getArea (cityId) { ... return new Promise(resolve => { resolve(areaData) } } 复制代码
如果用 Promise
实现是这样:
getProvinces().then(provinceId => getCitys(provinceId)).then(cityId => getArea(cityId) 复制代码
再来看看用 async/await
实现方式:
async getData () { const provinceId = await getProvinces() const cityId = await getCitys(provinceId) return await getArea(cityId) } getData() 复制代码
虽然两种方法都能够达到我们最终的目的,但是在依赖关系更加复杂的情况下,使用 Promise
的方式会使得链式非常的长,并且相比使用 async/await
代码阅读性会更低。
async运行过程
在工作中 async
的应用情况更加多种,因为其看似同步的处理异步操作,解决了不断回调的问题,增加了代码的可阅读性。 async
虽然看似同步操作,但是它式非阻塞的,接下来将 async
、 Promise
和 setTimeout
结合,用一个小例子加深对 async
的理解:
async function asyncFn1 () { console.log('asyncFn1 start') await asyncFn2() console.log('async1 end') } async function asyncFn2 () { console.log('asyncFn2') } console.log('script start') setTimeout(function () { console.log('setTimeout') }, 0) asyncFn1() new Promise((resolve) => { console.log('Promise') resolve() }).then(() => { console.log('Promise.then') }) console.log('script end') 复制代码
上面的代码,运行过程中会打印出8条语句,请大家先花一些时间思考一下执行顺序。
最终在控制台中的打印结果为:
script start asyncFn1 start asyncFn2 Promise script end Promise.then async1 end setTimeout 复制代码
应该有许多人的答案都是正确的,假如你的答案与正确答案有些许偏差,也没关系,通过这道题你能更深入的理解异步执行的问题,这段代码的执行顺序其实是这样的:
- 定义异步的asyncFn1函数
- 定义异步的asyncFn2函数
- 执行console.log('script start')语句 * 1
- 定义一个定时器在0ms后输出(setTimeout会被加入到macrotasks队列中,所以执行优先级比被加入microtasks队列的低)
- 执行asyncFn1函数 :
(1)执行console.log('asyncFn1 start')语句 * 2
(2)遇到await,执行asyncFn2函数 * 3(此时让出线程,跳出asyncFn1函数,继续执行同步栈的任务)
- 执行Promise语句
(1)执行console.log('Promise')语句 * 4
(2)resolve(),返回一个Promise对象,将这个Promise对象加入到microtasks队列中
- 执行console.log('script end')语句 * 5
- 同步栈执行完毕
- 回到asyncFn1函数体中,将asyncFn2函数返回的Promise对象加入到microtasks队列中
- 取出microtasks队列中的任务,打印console.log('Promise.then') * 6
- 接着执行asyncFn1函数体中console.log('asyncFn1 end')语句 * 7
- 最后执行macrotasks队列中的任务,执行console.log('setTimeout') * 8
以上是我对async/await知识的一些拙见,写下这篇文章单纯为了巩固自身的知识,希望也能对读者有一点点帮助。
本文总结参考自:阮一峰的ESMAScript6入门以及 前端er,你真的会用async吗?
以上所述就是小编给大家介绍的《理解 async/await》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 理解原型其实是理解原型链
- 要理解深度学习,必须突破常规视角去理解优化
- 深入理解java虚拟机(1) -- 理解HotSpot内存区域
- 荐 【C++100问】深入理解理解顶层const和底层const
- 深入理解 HTTPS
- 深入理解 HTTPS
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The NSHipster Fake Book (Objective-C)
Mattt Thompson / NSHipster Portland, Oregon / 2014 / USD 19.00
Fake Books are an indispensable tool for jazz musicians. They contain the melody, rhythm, and chord changes for hundreds of standards, allowing a player to jump into any session cold, and "fake it" th......一起来看看 《The NSHipster Fake Book (Objective-C)》 这本书的介绍吧!