在作項目的時候,常常會碰到關於異步的問題,遇到多個異步請求,又要控制其順序,該怎麼辦?涉及多個回調造成回調地獄又該如何處理?ES2017 標準引入了 async 函數,使得異步操做變得更加方便。本文主要從async/await的基本用法、平行任務、注意事項幾個方面來介紹。javascript
ES6 誕生之前,異步編程的方法,大概有下面四種。html
首先咱們經過一個例子,分別使用async/await與傳統異步方法進行編寫,進而對async/await寫法有個初步的瞭解。java
先定義一個 Fetch 方法用於獲取信息:git
function fetchUser(){
return new Promise((resolve, reject) => {
fetch('https://whuzxq.com/userList/4356')
.then((data) => {
resolve(data.json());
}, (error) => {
reject(error);
})
});
}
複製代碼
Promise方式es6
function getuserByPromise(){
fetchUser()
.then((data) => {
console.log(data);
}, (error) => {
console.log(error);
})
}
複製代碼
Promise的方式雖然解決了回調地獄,可是整段代碼充滿then,語義化不明顯,代碼流程不能很好的表示執行流程。github
async 方式編程
/** * async 方式 */
async function getUserByAsync(){
let user = await fetchUser();
return user;
}
getUserByAsync()
.then(v => console.log(v));
複製代碼
async 函數完美的解決了上面兩種方式的問題。同時 async 函數自帶執行器,執行的時候無需手動加載。json
經過以上兩個例子,應該對async的使用有了一個初步的認識,下文將詳細列出async/await相關的重要知識點。promise
1.async函數返回一個promise對象。併發
2.async函數內部return語句返回的值,會成爲then方法回調函數的參數。
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
複製代碼
3.async函數返回的 Promise 對象,必須等到內部全部await命令後面的 Promise 對象執行完,纔會發生狀態改變,除非遇到return語句或者拋出錯誤。也就是說,只有async函數內部的異步操做執行完,纔會執行then方法指定的回調函數。
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
複製代碼
上面代碼中,函數getTitle內部有三個操做:抓取網頁、取出文本、匹配頁面標題。只有這三個操做所有完成,纔會執行then方法裏面的console.log。
須要理解如下要點: 1.正常狀況下,await命令後面是一個 Promise 對象。若是不是,會被轉成一個當即resolve的 Promise 對象。
async function f() {
return await 123;
}
f().then(v => console.log(v))
// 123
複製代碼
2.只要一個await語句後面的 Promise 變爲reject,那麼整個async函數都會中斷執行。
async function f() {
await Promise.reject('出錯了');
await Promise.resolve('hello world'); // 不會執行
}
複製代碼
有時,咱們但願即便前一個異步操做失敗,也不要中斷後面的異步操做。這時能夠將第一個await放在try...catch結構裏面,這樣無論這個異步操做是否成功,第二個await都會執行。
async function f() {
try {
await Promise.reject('出錯了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
複製代碼
另外一種方法是await後面的 Promise 對象再跟一個catch方法,處理前面可能出現的錯誤。
async function f() {
await Promise.reject('出錯了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// 出錯了
// hello world
複製代碼
1.前面已經說過,await命令後面的Promise對象,運行結果多是rejected,因此最好把await命令放在try...catch代碼塊中。
2.await命令只能用在async函數之中,若是用在普通函數,就會報錯。
進行 JavaScript 異步編程時,你們常常須要逐一編寫多個複雜語句的代碼,並都在調用語句前標註了 await。因爲大多數狀況下,一個語句並不依賴於前一個語句,可是你仍不得不等前一個語句完成,這會致使性能問題。所以,多個await命令後面的異步操做,若是不存在繼發關係,最好讓它們同時觸發。
let foo = await getFoo();
let bar = await getBar();
複製代碼
上面代碼中,getFoo和getBar是兩個獨立的異步操做(即互不依賴),被寫成繼發關係。這樣比較耗時,由於只有getFoo完成之後,纔會執行getBar,徹底可讓它們同時觸發。
// 寫法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 寫法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
複製代碼
實例一:假定某個 DOM 元素上面,部署了一系列的動畫,前一個動畫結束,才能開始後一個。若是當中有一個動畫出錯,就再也不往下執行,返回上一個成功執行的動畫的返回值。
async function chainAnimationsAsync(elem, animations) {
let ret = null;//變量ret用來保存上一個動畫的返回值
try {
for(let anim of animations) {
ret = await anim(elem);
}
} catch(e) {
/* 忽略錯誤,繼續執行 */
}
return ret;
}
複製代碼
實例二:實際開發中,常常遇到一組異步操做,須要按照順序完成。好比,依次遠程讀取一組 URL,而後按照讀取的順序輸出結果。
async function logInOrder(urls) {
for (const url of urls) {
const response = await fetch(url);
console.log(await response.text());
}
}
複製代碼
上面代碼確實大大簡化,問題是全部遠程操做都是繼發。只有前一個 URL 返回結果,纔會去讀取下一個 URL,這樣作效率不好,很是浪費時間。咱們須要的是併發發出遠程請求。
async function logInOrder(urls) {
// 併發讀取遠程URL
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});
// 按次序輸出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}
複製代碼
上面代碼中,雖然map方法的參數是async函數,但它是併發執行的,由於只有async函數內部是繼發執行,外部不受影響。後面的for..of循環內部使用了await,所以實現了按順序輸出。
http://es6.ruanyifeng.com/#docs/async https://github.com/xitu/gold-miner/pull/3738/files https://juejin.im/post/596e142d5188254b532ce2da https://juejin.im/post/5ade84c951882567113ad246
原文可訪問個人博客