1. async/await是什麼?
async 是一個修飾符,async 定義的函數會預設的傳回一個Promise對象resolve的值,是以對async函數可以直接進行then操作,傳回的值即為then方法的傳入函數。
await 也是一個修飾符,await 關鍵字 隻能放在 async 函數内部, await關鍵字的作用 就是擷取 Promise中傳回的内容, 擷取的是Promise函數中resolve或者reject的值。
那麼async/await到底是幹嘛的呢?
1.async/await 是一種編寫異步代碼的新方法。之前異步代碼的方案是回調和 promise。
2.async/await 是建立在 promise 的基礎上。(如果對Promise不熟悉,我已經着手在寫Promise的文章了)
3.async/await 像 promise 一樣,也是非阻塞的。
4.async/await 讓異步代碼看起來、表現起來更像同步代碼。這正是其威力所在。
async/await其實是Promise的文法糖,它能實作的效果都能用then鍊來實作,這也和我們之前提到的一樣,它是為優化then鍊而開發出來的。從字面上來看,async是“異步”的簡寫,await譯為等待,是以我們很好了解async聲明function是異步的,await等待某個操作完成。當然文法上強制規定await隻能出現在asnyc函數中,我們先來看看async函數傳回了什麼:
async function testAsy(){
return 'hello world';
}
let result = testAsy();
console.log(result)
複制

這個async聲明的異步函數把return後面直接量通過Promise.resolve()傳回Promise對象,是以如果這個最外層沒有用await調用的話,是可以用原來then鍊的方式來調用的:
async function testAsy(){
return 'hello world'
}
let result = testAsy()
console.log(result)
result.then(v=>{
console.log(v) //hello world
})
複制
聯想一下Promise特點——異步無等待,是以當沒有await語句執行async函數,它就會立即執行,傳回一個Promise對象,非阻塞,與普通的Promise對象函數一緻。
以下是一個promise在1s之後resolve的例子:
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('done!'), 1000)
})
let result = await promise // 直到promise傳回一個resolve值(*)
console.log(result)
}
f()
複制
函數執行到(*)行會‘暫停’,當promise處理完成後重新恢複運作, resolve的值成了最終的result,是以上面的代碼會在1s後輸出’done!’
我們強調一下:await字面上使得JavaScript等待,直到promise處理完成,
然後将結果繼續下去。這并不會花費任何的cpu資源,因為引擎能夠同時做其他工作:執行其他腳本,處理事件等等。
這隻是一個更優雅的得到promise值的語句,它比promise更加容易閱讀和書寫。
如果去掉await,我們獲得的就是一個promise對象。
//加上await
done!
//沒有await
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: "done!"
複制
重點就在await,它等待什麼呢?
按照文法說明,await等待的是一個Promise對象,或者是其他值(也就是說可以等待任何值),如果等待的是Promise對象,則傳回Promise的處理結果;如果是其他值,則傳回該值本身。并且await會暫停目前async function的執行,等待Promise的處理完成。若Promise正常處理(fulfillded),其将回調的resolve函數參數作為await表達式的值,繼續執行async function;若Promise處理異常(rejected),await表達式會把Promise異常原因抛出;另外如果await操作符後面的表達式不是一個Promise對象,則傳回該值本身。
2. 深入了解async/await
我們來詳細說明一下async/await的作用。await操作符後面可以是任意值,當是Promise對象的時候,會暫停async function執行。也就是說,必須得等待await後面的Promise處理完成才能繼續:
function testAsy(x){
return new Promise(resolve=>{setTimeout(() => {
resolve(x);
}, 3000)
}
)
}
async function testAwt(){
let result = await testAsy('hello world');
console.log(result); // 3秒鐘之後出現hello world
}
testAwt();
複制
await 表達式的運算結果取決于它等的東西。
如果它等到的不是一個 Promise 對象,那 await 表達式的運算結果就是它等到的東西。
如果它等到的是一個 Promise 對象,await 就忙起來了,它會阻塞後面的代碼,等着 Promise 對象 resolve,然後得到 resolve 的值,作為 await 表達式的運算結果。
我們再把上面的代碼修改一下,好好體會“阻塞”這個詞
function testAsy(x){
return new Promise(resolve=>{setTimeout(() => {
resolve(x);
}, 3000)
}
)
}
async function testAwt(){
let result = await testAsy('hello world');
console.log(result); // 3秒鐘之後出現hello world
console.log('Y') // 3秒鐘之後出現Y
}
testAwt();
console.log('L') //立即輸出L
複制
這就是 await 必須用在 async 函數中的原因。async 函數調用不會造成阻塞,它内部所有的阻塞都被封裝在一個 Promise 對象中異步執行。await暫停目前async的執行,是以’tangSir’'最先輸出,hello world’和‘tangj’是3秒鐘後同時出現的。
為什麼會立即輸出L,這就涉及到了JS中的事件循環了,我寫了一篇關于事件循環的部落格,看了應該會明白,總的來說,異步函數會在非異步函數之後運作。
3. async和await簡單應用
上面已經說明了 async 會将其後的函數(函數表達式或 Lambda)的傳回值封裝成一個 Promise 對象,而 await 會等待這個 Promise 完成,并将其 resolve 的結果傳回出來。
現在舉例,用 setTimeout模拟耗時的異步操作,先來看看不用 async/await 會怎麼寫
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve("long_time_value"), 1000);
});
}
takeLongTime().then(v => {
console.log("got", v); //一秒鐘後輸出got long_time_value
});
複制
如果改用 async/await 呢,會是這樣
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve("long_time_value"), 1000);
});
}
async function test() {
const v = await takeLongTime();
console.log(v); // 一秒鐘後輸出long_time_value
}
test();
複制
tankLongTime()本身就是傳回的 Promise 對象,是以加不加 async結果都一樣。
4. 處理then鍊
前面我們說了,async和await是處理then鍊的文法糖,現在我們來看看具體是怎麼實作的:
假設一個業務,分多個步驟完成,每個步驟都是異步的,而且依賴于上一個步驟的結果。我們仍然用setTimeout來模拟異步操作:
/**
* 傳入參數 n,表示這個函數執行的時間(毫秒)
* 執行的結果是 n + 200,這個值将用于下一步驟
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
複制
現在用 Promise 方式來實作這三個步驟的處理。
function doIt(){
console.time('doIt');
let time1 = 300;
step1(time1)
.then((time2) => step2(time2))
.then((time3) => step3(time3))
.then((result) => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
})
}
doIt();
//執行結果為:
//step1 with 300
//step2 with 500
//step3 with 700
//result is 900
//doIt: 1510.2490234375ms
複制
輸出結果 result 是 step3() 的參數 700 + 200 = 900。doIt() 順序執行了三個步驟,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 計算的結果一緻。
如果用 async/await 來實作呢,會是這樣:
async function doIt() {
console.time('doIt');
let time1 = 300;
let time2 = await step1(time1);//将Promise對象resolve(n+200)的值賦給time2
let time3 = await step1(time2);
let result = await step1(time3);
console.log(`result is ${result}`);
console.timeEnd('doIt');
}
doIt();
//執行結果為:
//step1 with 300
//step2 with 500
//step3 with 700
//result is 900
//doIt: 1512.904296875ms
複制
顯然我們用async/await簡單多了。
5. Promise處理結果為rejected
await 指令後面的 Promise 對象,運作結果可能是 rejected,是以最好把 await 指令放在 try…catch 代碼塊中。
async function myFunction() {
try {
await somethingThatReturnAPromise();
} catch (err){
console.log(err);
}
}
//另一種寫法
async function myFunction() {
await somethingThatReturnAPromise().catch(function(err) {
console.log(err);
})
}
複制
6.執行個體
Vue普通寫法:
methods: {
getLocation(phoneNum) {
return axios.post('/mm接口', {
phoneNum
})
},
getFaceList(province, city) {
return axios.post('/nn接口', {
province,
city
})
},
getFaceResult () {
this.getLocation(this.phoneNum).then(res => {
if (res.status === 200 && res.data.success) {
let province = res.data.obj.province;
let city = res.data.obj.city;
this.getFaceList(province, city).then(res => {
if(res.status === 200 && res.data.success) {
this.faceList = res.data.obj
}
})
}
}).catch(err => {
console.log(err)
})
}
}
複制
這時你看到了then 的鍊式寫法,有一點回調地域的感覺。現在我們在有async/ await 來改造一下。
加入async/ await:
首先把 getFaceResult 轉化成一個async 函數,就是在其前面加async, 因為它的調用方法和普通函數的調用方法是一緻,是以沒有什麼問題。然後就把 getLocation 和getFaceList 放到await 後面,等待執行, getFaceResult 函數修改如下:
async getFaceResult () {
let location = await this.getLocation(this.phoneNum);
if (location.data.success) {
let province = location.data.obj.province;
let city = location.data.obj.city;
let result = await this.getFaceList(province, city);
if (result.data.success) {
this.faceList = result.data.obj;
}
}
}
複制
現在代碼的書寫方式,就像寫同步代碼一樣,沒有回調的感覺,非常舒服。
現在就還差一點需要說明,那就是怎麼處理異常,如果請求發生異常,怎麼處理? 它用的是try/catch 來捕獲異常,把await 放到 try 中進行執行,如有異常,就使用catch 進行處理。
async getFaceResult () {
try {
let location = await this.getLocation(this.phoneNum);
if (location.data.success) {
let province = location.data.obj.province;
let city = location.data.obj.city;
let result = await this.getFaceList(province, city);
if (result.data.success) {
this.faceList = result.data.obj;
}
}
} catch(err) {
console.log(err);
}
}
複制
注:
ES7引入的關鍵字async/await是對JavaScript異步程式設計的改進。它可以使代碼更容易閱讀和調試。然而,為了正确使用它們,必須完全了解promise,因為它們隻不過是文法糖,而潛在的技術仍然是promise。