在JavaScript裡面,Async和Await應該是搜尋熱度最高的關鍵字了,因為他們相對複雜。我們一步步討論這件事的歷史 — 它們為什麼出現,解決了什麼問題。
首先我們必須了解JavaScript執行的基本原則 — synchronous(同步)。大部分的JavaScript程式都是synchronous的,意味著一行程式碼執行完才會再執行下一行。「同步」這個中文其實有點誤導人,程式碼並不是「同時」執行,而是一行接著一行執行。這會導致什麼問題呢?如果有一行程式碼執行的特別久,後續的就會受影響而延後執行。這個概念被稱為blocking(阻塞)。
我們舉例說明:
我們創建一個function,讓它在3秒內持續運作loop,直到時間到才會結束。執行後我們可以看到Console印出的順序是這樣。
這個例子呈現了因為synchronous的特性,console.log(”End”)
這一行被blocked(阻塞)了。
如果JavaScript都只能這樣運作,問題就大了。一個網站常常需要等候外部傳資料進來,如果在這期間什麼其他程式碼都不能執行的話,使用者很快就離開了。舉例來說,我們點開instagram貼文,在等待資料期間,需要執行程式來show loading或執行其他動畫等等,這些都需要程式碼在等待的時候能夠正常運作。
換句話說,我們非常需要asynchronous(非同步)的運作方式,也就是在某些情況下,程式碼可以獨立運作,其他人不用等它結束才能行動。
但同時,我們也需要保留synchronous的運作方式,想像一下instagram至少要等到你的資料到齊了,才能執行「顯示出貼文」這段程式碼。所以我們需要一些方式來達成「那些是需要非同步的,這些不用」的機制。
JavaScript中做到asynchronous的方法不少,而且經歷了幾個階段,async
和await
就是其中比較近期的。我們會依照時間線來介紹這些方法以及他們為何被取代。
又一個新的function名稱,但別緊張。callback function指的就是「在某件事發生之後,再執行的function」,我們以前就看過。
在’click’
之類的事件發生後,再執行的function就是callback function。
監聽一個事件還好,但如果我們的步驟有4個呢?假設我們需要按照順序讀取4份文件。因為要按照順序,所以我們在每個讀取檔案的function裡加上callback去讀取下一份,看起來會像這樣:
你可以想像如果我們有10個步驟要做情況會有多慘烈。一層包著一層的情況非常難理解,而且程式碼本身會變成一個三角形,非常難閱讀。這種情況被稱作callback hell(地獄)。所以callback不是很好的辦法,慢慢有更好的方法出現,那就是Promise
。
Promise是JavaScript裡面很特殊的物件,它需要用關鍵字new
來創造,基礎語法大概是這樣。
Promise
創建式子裡會有一個function,裡面第一個參數是成功時會做的事,第二的是失敗時會做的事。至於什麼情況會成功跟失敗呢,通常這些都是跟伺服器要求資料的時候會用到的工具,伺服器會回傳成功會失敗的資訊給你。現在的重點是知道它的語法如何勝過callback。
接下來繼續改寫這個例子:
一個Promise
後面可以接then()
和catch()
,這些是內建的method。resolve()
和reject()
的參數會被傳過去。以上述的例子來說,當Promise
成功,success
就會等於”Promise結果成功”
。如果失敗fail
就會等於”Promise失敗原因”
(成功的話catch()就不會執行了)。
這裡一次會看見很多東西,包含之前提過的簡寫arrow function還有chain的概念。在Promise
後面接的那些(包含then()
和catch()
),會獨自運行(asynchronous),不會block下面的程式碼。
更重要的是,這個then()
,可以寫很多串接下去。我們再討論回剛才讀取書籍,把readFile()
改成Promise
的版本。
在之前我們看見array一直接下去,重點是array必須回傳array才能一直接下去。這裡我們想讓readFilePromise()
成為一個chain,就要讓他回傳readFilePromise()
,而每個Promise
又都有then()
可以用,所以可以一直接下去。
從這個改寫範例我們看到,這樣不管寫幾項,程式碼都還是相對好閱讀。Promise
雖然已經優化了asynchronous的撰寫,但你也看得出來,它其實也沒那麼好寫,有一堆return和其他關鍵字(不過Promise還是滿常見的)。async
和await
更加簡化了這個流程。
async
和await
講了這麼久終於到了今天的主題,async
和await
。它們兩個關鍵字的出現讓撰寫非同步程式更像寫同步程式。他們還是需要一個Promise,可以理解成「處理Promise一種更好寫的語法」。
我們只需要在需要用到asynchronous的function前面加上關鍵字async
(非同步)就可以了。async function整個都會獨立運作,不會block其他程式的運行。而裡面的每一行就像synchronous一樣一行一行的運作。第一章內容讀取之後,接著第二章以此類推。
值得注意的是,在回傳Promise
的函數前加上await
(等待)才可以確保每一步都等候結果再進行,正如同它的字面意思,我們在命令JS等這個Promise
完成,這可能更符合邏輯需求。如果不需要按照順序,則可以略過await
,讓其他操作繼續運行。
如果那個function跟Promise
沒有關係,它本來就會依照順序執行,不用特別加await
。
總而言之,async
和await
提供了一種更自然的方式來處理asynchronous,更像原本synchronous的寫法。雖然還是有一些學習成本跟額外的規則,像是我們還是需要Promise以及特定位置await
的使用等等,但回頭看看callback function,你能了解它的益處。
async
和await
現在已經是相對主流處理asynchronous的方式了,雖然其中還是有些比較不直觀的部分,但比起callback hell和冗長的Promise chain,async
和await
已經提供了一種相對易於閱讀、撰寫的解決方案。今天的內容比較深入,但不用擔心。掌握這些語法的優缺點,理解async
和await
的出現如何簡化了asynchronous,已經是很大的一步了。即使一開始有些不直覺,隨著練習和應用,你會逐漸掌握它們的用法,慢慢進步。
最後,我們會用先前所學的來製作一個簡單的To-Do List App,對於初學JavaScript的人來說,To-Do List真的是一個經典練習。裡面會包含許多關鍵的JS應用,即使是大型的網站產品,也常常需要這些語法。透過這個實作練習,你將會更直觀地體驗JavaScript的實際應用。
我是Erkin, 一個網站開發者。
有任何疑問或是想勘誤的話歡迎聯繫。