我們都知道使用Promise能很好地解決回調地獄的問題,但如果處理流程比較複雜的話,那麼整段代碼将充斥着then,語義化不明顯,代碼不能很好地表示執行流程,那有沒有比Promise更優雅的異步方式呢?
假如有這樣一個使用場景:需要先請求a連結,等傳回資訊之後,再請求b連結的另外一個資源。下面代碼展示的是使用fetch來實作這樣的需求,fetch被定義在window對象中,它傳回的是一個Promise對象
fetch('./data1.json')
.then(res1 => {
console.log(res1)
return fetch('./data2.json')
})
.then(res2 => {
console.log(res2)
})
.catch(err => {
console.log(err)
})
雖然上述代碼可以實作這個需求,但語義化不明顯,代碼不能很好地表示執行流程。基于這個原因,ES8引入了async/await,這是JavaScript異步程式設計的一個重大改進,提供了在不阻塞主線程的情況下使用同步代碼實作異步通路資源的能力,并且使得代碼邏輯更加清晰。
async function foo () {
try {
let res1 = await fetch('./data1.json')
console.log(res1)
let res2 = await fetch('./data2.json')
console.log(res2)
} catch (err) {
console.error(err)
}
}
foo()
通過上面代碼,你會發現整個異步處理的邏輯都是使用同步代碼的方式來實作的,而且還支援try catch來捕獲異常,這感覺就在寫同步代碼,是以是非常符合人的線性思維的。需要強調的是,await 不可以脫離 async 單獨使用,await 後面一定是Promise 對象,如果不是會自動包裝成Promise對象。
async/await特點
- async/await更加語義化,async 是“異步”的簡寫,async function 用于申明一個 function 是異步的; await,可以認為是async wait的簡寫, 用于等待一個異步方法執行完成;
- async/await是一個用同步思維解決異步問題的方案(等結果出來之後,代碼才會繼續往下執行)
- 可以通過多層 async function 的同步寫法代替傳統的callback嵌套
async function文法
- 自動将正常函數轉換成Promise,傳回值也是一個Promise對象
- 隻有async函數内部的異步操作執行完,才會執行then方法指定的回調函數
- 異步函數内部可以使用await
await文法
- await 放置在Promise調用之前,await 強制後面點代碼等待,直到Promise對象resolve,得到resolve的值作為await表達式的運算結果
- await隻能在async函數内部使用,用在普通函數裡就會報錯
根據MDN定義,async是一個通過異步執行并隐式傳回Promise作為結果的函數。
async function foo () {
return 'info'
}
foo().then(val => {
console.log(val) // info
})
上述代碼,我們可以看到調用async 聲明的foo 函數傳回了一個Promise對象,等價于下面代碼:
async function foo () {
return Promise.resolve('info')
}
foo().then(val => {
console.log(val) // info
})
async function
用來定義一個傳回
AsyncFunction
對象的異步函數。異步函數是指通過事件循環異步執行的函數,它會通過一個隐式的
Promise
傳回其結果。
function resolveAfter1Second() {
return new Promise(resolve => {
setTimeout(function() {
resolve("fast");
}, 1000);
});
};
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
var result = await resolveAfter2Seconds()
console.log(result);
}
asyncCall();
錯誤處理
在 async 函數裡,無論是 Promise rejected 的資料還是邏輯報錯,都會被默默吞掉, 是以最好把 await 放入try{}catch{}中,catch能夠捕捉到 Promise 對象rejected的資料或者抛出的異常.
使用場景
多個await指令的異步操作,如果不存在依賴關系(後面的await不依賴前一個await傳回的結果),用Promise.all()讓它們同時觸發
async function foo() {
console.log('foo start:',Date.now())
let [res1, res2] = await Promise.all([resolveAfter1Seconds(), resolveAfter2Seconds()])
console.log('foo end:', Date.now())
}
foo();
Async/Await的支援情況: