如今面对平常工做时,总避免不了面对异步操做带来的一些麻烦。在时代演变的过程当中,处理异步的方法有许多种:回调函数、Promise
链式语法、Generator
函数到如今比较流行的 async
函数。那什么是 async
呢?javascript
async
函数是 Generator
函数的语法糖。使用 async
关键字代替 Generator
函数的星号 *
,await
关键字代替 yield
。相较于Generator函数,async函数改进了如下四点:前端
Generator
函数的执行必须靠执行器,因此才有了 co
模块,而 async
函数自带执行器。async
和 await
,比起 *
和 yield
,语义更清楚。async
表示函数里有异步操做,await
表示紧跟在后面的表达式须要等待结果。co
模块约定,yield
命令后面只能是Thunk 函数或Promise 对象,而async
函数的await
命令后面,能够是Promise对象和原始类型的值。async
函数的返回值是Promise对象,这比Generator 函数的返回值是 Iterator对象方便多了。你能够用 then
方法指定下一步的操做。关于async的用法,先看一个简单的小例子:java
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'。es6
了解了 async 的基本用法,接下来理解一下 async 的运做:bash
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
当 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
进行错误处理,基本的错误处理方式有两类:ui
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
的魔力。spa
假设咱们如今要获取一个地级市拥有多少个辖区,咱们如今得先调用获取当地省份的接口,从中拿到省份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
、 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
复制代码
应该有许多人的答案都是正确的,假如你的答案与正确答案有些许误差,也不要紧,经过这道题你能更深刻的理解异步执行的问题,这段代码的执行顺序实际上是这样的:
(1)执行console.log('asyncFn1 start')语句 * 2
(2)遇到await,执行asyncFn2函数 * 3(此时让出线程,跳出asyncFn1函数,继续执行同步栈的任务)
(1)执行console.log('Promise')语句 * 4
(2)resolve(),返回一个Promise对象,将这个Promise对象加入到microtasks队列中
以上是我对async/await知识的一些拙见,写下这篇文章单纯为了巩固自身的知识,但愿也能对读者有一点点帮助。
本文总结参考自:阮一峰的ESMAScript6入门以及前端er,你真的会用async吗?