如今面對平常工做時,總避免不了面對異步操做帶來的一些麻煩。在時代演變的過程當中,處理異步的方法有許多種:回調函數、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嗎?