我眼中的async/await

blank

我眼中的async/await

async / await是ES2017中引入的,為了使異步操作得更加方便,本質上async函數是Generator函數的語法糖。

async函數書寫的方式跟我們普通的函數書寫方式一樣,只不過是前面多了一個async關鍵字,並且函數返回的是一個Promise對象,所接收的值就是函數return的值。

letfn=asyncfunction(name){returnname;}fn('小明').then(name=>console.log(name));// 小明

async函數內部可以使用await命令,表示等待一個異步函數的返回。 await後面跟著的是一個Promise對象,如果不是的話,系統會調用Promise.resolve()方法,將其轉為一個resolve的Promise的對象。

letfoo=asyncfunction(){returnawait1;}foo().then(obj=>console.log(obj));// 1
// 下面将会在 1000 毫秒之后输出 hello world
letbar=asyncfunction(){returnawaitnewPromise(resolve=>{setTimeout(()=>resolve('hello world'),1000)});}bar().then(src=>console.log(src));

如果async函數當中執行出現錯誤的話,返回的Promise就會被reject

letfn=asyncfunction(){throw'reject';}fn().catch(err=>console.log(err));// reject

並且,如果await後面的Promise的狀態是reject ,那麼整個async函數就會中斷執行,錯誤會被async函數的catch捕獲到。

letfoo=asyncfunction(){awaitPromise.reject(1);}foo().catch(err=>console.log(err))// 1

所以我們用來操作異步請求時,有可能會出現請求失敗的情況,這個時候為了防止函數停止運行,我們需要一個try...catch結構來處理錯誤代碼。

letbar=asyncfunction(){try{awaitPromise.reject('error')}catch(e){console.log(e)}}

上面的寫法就保證了我們的異步函數不會因為出錯而中斷執行。

以上就是async函數的簡單用法,如果大家想要深入了解的話,推薦大家閱讀阮一峰老師的《ES6標准入門》。

你以為我會就這麼快就水完了?那是不可能的。說起異步的話,我們都知道js 在執行的時候只有一個主線程,主線程會不停的讀取調用棧。

這個時候我們就要說到setTimeout(fn,0)了,在我眼中這段代碼的意思是盡快的加入當前的調用棧,只要執行完前面的任務,就會來執行它。我們可以腦補一下這個畫面。 setTimeout函數對著js引擎說:大哥,我這兒任務比較緊急,讓我插插隊唄。 js引擎大哥就不耐煩的說:知道了,知道了,後面排隊去,前面的完了就到你了。

所以就出現了下面這段代碼:

letfn=asyncfunction(){letnum=await1;console.log(num)num++;returnnum;}fn().then(num=>console.log(num))setTimeout(()=>console.log(100),0);

猜猜看,會依次輸出什麼?

起初,我認為會輸出100 ,1 ,2 。但其實最終結果是1,2,100 ,這不論是在node環境還是chrome下都是如此。後來我自信思考了一下,我們await後面跟著是一個resolve的Promise對象,本質上還是同步的代碼,所以該async函數就如同普通函數一樣執行。

我們再改造一下上面的代碼:

letfn=asyncfunction(){letnum=await1;console.log(num)awaitnewPromise(resolve=>setTimeout(()=>resolve(++num)),0);console.log(num)returnnum;}fn().then(num=>console.log(num))setTimeout(()=>console.log(100),0);

現在這個代碼會依次輸出啥?

踩過了上面的那個坑之後,我仔細想了一下,我們async函數內部的setTimeout雖說是盡快排隊,但是await命令會在此暫停住,繼續往下執行代碼,將下面的setTimeout先排上隊,然後再將async內部的排上隊。所以這邊的代碼輸出的是: 1,100,2,2

JavaScript在發展過程中,共經歷了回調函數、 Promise對象、 Generator函數, async函數來處理異步。我們接下來就來看一下async函數如何更優雅的處理異步。

假設我們需要分別讀取a、b、c 三個文件,具體代碼如下:

constfs=require('fs');//對fs模塊進行Promise封裝constreadFile=function(src){returnnewPromise((resolve,reject)=>{fs.readFile(src,(err,data)=>{if(err)reject(err);resolve(data);})})}// Promise的寫法readFile('./a.txt').then(data=>{console.log(data.toString());returnreadFile('./b.txt');}).then(data=>{console.log(data.toString());returnreadFile('./c.txt');}).then(data=>{console.log(data.toString());})// Generator函數寫法function*ascReadFile(){yieldreadFile('./a.txt');yieldreadFile('./b.txt');yieldreadFile('./c.txt');}letg=ascReadFile();g.next().value.then(data=>{console.log(data.toString());returng.next().value;}).then(data=>{console.log(data.toString());returng.next().value;}).then(data=>{console.log(data.toString());})// async函數寫法asyncfunctionasyncReadFile(){leta=awaitreadFile('./a.txt');console.log(a.toString());letb=awaitreadFile('./b.txt');console.log(b.toString());letc=awaitreadFile('./c.txt');console.log(c.toString());}asyncReadFile();

上面是一個簡化版的代碼,省略了錯誤處理。通過上面代碼的對比,我們可以看出來async函數比起Promise的鍊式操作,以及Generator的手動執行,要方便得太多了,代碼上也簡潔明了,讓我們看起來一目了然。

上面就是我眼中的async函數,以及我所理解的異步處理方法。如果大家對我所理解的有任何歧義,歡迎大家來一起探討。

What do you think?

Written by marketer

blank

前端神器:一行命令,React 組件轉Vue 組件!

blank

基於Immutable.js 實現撤銷重做功能