async 是什麼 & async的基本用法
async function
宣告用於定義一個返回AsyncFunction
物件的非同步函式。非同步函式是指通過事件迴圈非同步執行的函式,它會通過一個隱式的Promise
返回其結果。但是如果你的程式碼使用了非同步函式,它的語法和結構會更像是標準的同步函式。 引用自MDN。
js的方法和語法糖多數都是語義化的,從字面意思上來說,async代表非同步的
,用來表示一個非同步的函式,返回一個promise
,可以使用then方法新增回撥。
可以看下這個例子:
const foo = async () => {
return { name: '芬達' }
}
console.log(foo())
複製程式碼
可以看到返回的是promise哦。這樣不就可以愉快的寫then式回撥了嘛。async
, 就會返回一個promise
。
await 與 async 搭配的基本用法
await 在等誰
await在英漢詞典中是動詞 等候 的意思,用法如下:
// 只能用在async函式中
const val = await promise
複製程式碼
await
可以讓js進行等待,直到promise
執行並返回結果時才會繼續往下執行。可以看一個小例子:
const foo2 = async () => {
const promise = new Promise((reslove, reject) => {
setTimeout(() => {
reslove('芬達')
}, 1000)
})
const ret = await promise
console.log(ret)
}
foo2()
複製程式碼
上面程式碼會在1s後列印芬達
。
從上面程式碼來說,await在等一個承諾,好吧,是promise
,更嚴謹的來說,他是在等待一個表示式,只不過這個表示式可以是promise,也可以是其他任意表示式的結果。所以,下面的程式碼是完全可以執行的:
const foo3 = () => '芬達'
const foo4 = async () => {
const ret = await foo3()
console.log(ret)
}
foo4()
複製程式碼
當然也是列印的芬達
。
await 等到了之後會做什麼
通俗點說,你可以把await看做是一個運算子號,如果它等到的不是promise,那麼他的運算結果就是當前它所等到的值,如果等到的是promise,那麼他會臨時阻塞後續程式碼,直到promise物件resolve,取到 resolve 的值,將其作為運算結果返回。
emmmmm, 就是這樣。
async 和 promise 的聯絡
async
可以看做是promise
與Generator
的語法糖,但對其做了改進。
- 內建執行器,
Generator
的執行必須依靠執行器,但async
的執行器與生俱來,使得async
函式與普通函式的呼叫別無二致。 - 更好的語義化,
就不做解釋了吧 - 返回值是promise,對開發者友好,感覺這個才是最重要的有木有。
它能為我們做什麼?
這個問題的答案應該是毋庸置疑的,必然是為了解決回撥地獄。async/await
的優勢就在於處理then式呼叫鏈呢。
我們可以先假設一個場景: 使用者登入之後拿到userId
,然後在去呼叫介面拿到token
,最後在其他介面的請求頭裡新增上token
欄位.....別問為什麼這麼白痴的設計,假設而已
初級前端的寫法:
ajax('login', { username, password }, ({ userId }) => {
ajax('getToken', { userId }, ({ token }) => {
ajax('getOtherInfo', { token }, res => {
// do something...
})
})
})
複製程式碼
是不是頭皮發麻,當然,如果你是寫這樣的程式碼的人,你可能覺得還可以接受,如果你是維護這樣的程式碼的,你就會明白有多難維護,這裡只寫了三層,實際中甚至更多。意味著層級巢狀,牽一髮而動全身,要改都得改的死衚衕,意味著程式碼縮排都能噁心壞你,所以,初級大圓滿前端是如何寫的呢?
ajax('login', { username, password })
.then(({ userId }) => ajax('getToken', { userId }))
.then(({ token }) => ajax('getOtherInfo', { token }))
.then(res => {
// do something...
})
複製程式碼
這樣一來,利用鏈式then呼叫,既可以清晰的展現api依賴關係,又可以優雅的縮排程式碼,但我們是講async
的啊老鐵,所以還是看下吧asynv
大法。
async function foo({ username, password }) {
const { userId } = await ajax('login', { username, password })
const { token } = await ajax('getToken', { userId })
const res = await ajax('getOtherInfo', { token })
// do something ...
}
複製程式碼
上面有提到,await
會臨時阻塞後續程式碼的執行,這就代表著介面呼叫會按照我們的程式碼順序"同步"執行(說是同步,實際上還是非同步請求,表現為同步行為是語法糖的原因)。這樣,程式碼就會按照我們的預期執行,介面依賴關係也更加明瞭,維護起來也是賞心悅目的。
貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/
它的優點和缺陷
上面有談到他一部分的優點,其實還有如下優點
- 語法簡潔,使程式碼可讀性更高
- 能使用
try catch
捕獲異常 - 使程式碼更加符合思維邏輯。
至於缺點呢...
- 需要babel編譯
- 缺少abort請求中斷,缺少非同步控制流程。
- 異常捕獲較為麻煩
- 沒有依賴關係的請求需要藉助promise.all
貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/貓咪分割線/
如何優雅的在async/await中處理錯誤
理論上來說,await
等到的可能是promise.reject,這種情況可以使用try/catch
來捕獲異常,ok沒毛病,像下面這樣
try {
const { userId } = await ajax('login', { username, password })
} catch {
throw new Error('no user found')
}
複製程式碼
But,如果像上面一樣有很多個await
呢,怎麼辦,每次都要寫一下嘛,這樣豈不是很難受?上週在沸點看到一位大佬對promise的異常處理,很有意義。如圖
async
也是可以藉助promise
來實現統一的異常捕獲。
function util(promise) {
return promise.then(data => [null, data]).catch(err => [err])
}
async function foo({ usernam, password }) {
let userId, token, err
;[err, { userId }] = await util(ajax('login', { username, password }))
// 因預設資料返回是物件,所以加了解構,部分省略...
// do something
}
複製程式碼
沒有大佬的考慮全面,但也足以應付日常需求了
簡單總結
async/await
總的來說,是一個優秀的非同步解決方案,利大於弊,值得一用。
因為我平時用async的頻率很低,所以專門總結了這篇文字。如果有什麼錯誤還請各位大佬指正。