前言

Asynchronous的中文翻譯是非同步、異步,Synchronous的中文翻譯是同步,以下都會直接用同步、非同步來介紹。

非同步程式設計是JavaScript在學習時一個很大的重點,不光是容易誤解,不好理解,也因為實際上要常常使用到。

這一篇不講太多專業術語跟程式範例,會試著用很白話的方式把非同步這件事情給講清楚,那就直接開始進入今天的主題!

JavaScript是單一執行緒

這是我覺得在了解同步跟非同步之前要先理解的第一個點,先完全不要管什麼是同步,什麼是非同步,一開始請先知道這個:

Javascript 程式執行,一次只能執行一件事情

就像是一家餐廳,就只有一個廚師,那他就只能一次煮一道菜,假如有兩個廚師,就是同時煮兩道菜,很多廚師可以同時煮很多菜那就是多執行緒,可以一次同時進行很多事情。

無論如何,要記住這家叫做「Javascript」的餐廳,永遠只有一個廚師(執行緒)。

JavaScript 同步跟非同步

剛開始學習時,同步會讓人感覺是,全部的動作同時開始進行,所以容易誤會(誤會同步=同時)。
後來去查閱一些資料後發現其實同步是 => 一次做一件事情。
既然同步是一次做一件事情,那非同步不就是一次做很多事情?

所以會發現到說很多教學文章會這樣寫:

同步:一次就只做一件事情
非同步:一次同時做很多件事情

使用”同時間”能處理事情的多寡來區分 JS 的同步跟非同步的文章真的很多。
可能這講法也沒有錯,但就會發生衝突,對剛學習沒多久的人造成心靈打擊,造成懂的人看得懂,不懂的人看不懂。

而我的理解 =>
單執行緒的JavaScript就像是餐廳的只有一個廚師,假如要說非同步是一次同時做很多件事情,就好似說突然冒出一堆廚師一樣,最主要發生的衝突,就是上面說的:

Javascript 程式執行,一次只能做一件事情

也許可以用很多個廚師,同時做很多事情的方法來做到非同步,但我JavaScript就只有一個廚師,怎麼辦呢?

先來看看另一個概念吧。

平行(Parallelism)與並行(Concurrency)

這個概念是我從執行資料平行處理的效能考量看到的,我覺得可以很好的解釋JavaScript如何在自身只能是單執行緒的情況下,達成到非同步的效果,同時又不會跟單執行緒的特性(一次做一件事情)發生衝突。

以下節錄文章概念:

平行(Parallelism)與並行(Concurrency)不同,如果兩個任務分配到一個 CPU 核心,在取得的時間片段中交互執行,稱之為並行。如果有兩個核心,兩個任務各分配到其中之一同時執行,稱之為平行。現今開發者對於並行設計應不陌生,利用並行運算處理多個流程,讓客戶端以為任務「同時」執行,或者是某任務被阻斷(Block)之時,切換另一任務執行,避免等待而浪費 CPU 執行時間,用以提升整體任務執行效率。

很好的解釋了一些盲點區域,上面有提到很多教學會講說非同步是一次同時做很多事情,其實那就是一種平行處理,確實也是一種非同步,但卻永遠不可能是JavaScript的非同步。

因為JavaScript這間餐廳的廚師就只有一個,要有很多人才能平行耶,我畫示意圖比較清楚。

多人廚師 => 平行處理 => 同時做很多菜 => 大家都很開心:

只有一個廚師 => 平行處理 => 同時做很多菜 => 廚師不開心 :

那就一個個慢慢做如何?

客人不開心。
而且中間要是過程有一個卡住,之後的菜也就都不用做了。

欸,那該怎麼辦,我大JavaScript餐廳就是只想請一個廚師,但又不想要廚師只能一個個做,那會花超久時間,聰明的大JavaScript餐廳想到了一個好辦法,剛剛那個是平行處理,那何不來試試並行處理?

並行處理:
交互執行,稱之為並行,要怎麼辦到這件事情,簡單來說就是不卡住,那是什麼意思?比如來說小白今天要做兩件事情,一個是烤土司一個是倒牛奶,而小白今天就只有一個人,所以沒辦法使用平行處理,除非小白會影分身。

除了慢慢來,先烤完吐司再倒牛奶之外,還有另外一種選擇,你可以先把你的吐司塞到烤土司機之後,不要在那邊傻傻等它,按下按鈕開始烤後,就衝去冰箱拿牛奶出來倒,倒完再看吐司好了沒。

這種做完後不停留到完全結束,就直接去做下一步的處理方式其實就是並行處理。

也因為這樣,所以看起來結果會很像是同時開始做,但是並沒有,可以來比較差異時間,請小白為我們示範一下,示意圖有請:

為什麼有一堆人會覺得 JS 非同步就是「同時」做很多事情,我覺得關鍵這就是在這邊,但因為單執行緒的觀念,就勢必不可能同時(平行處理),所以 JS 所謂的「非同步」操作,其實是比較偏向示意圖的第三種,也就是並行處理,這也是我個人所理解的 Asynchronous Programming (非同步程式設計)。

阻塞與非阻塞

根據上面的想法,我認為同步在處理事件的流程會被「卡住」,卡住的意思是說,當遇到沒辦法馬上執行完成的東西,就會整個停住,下一件要做的事情就會停擺。

而非同步在處理事件的流程不會被「卡住」,所以因為不會被卡住,所以看起來像是「一次同時做很多件事情」,但實際上沒有。

而這卡住不卡住其實比較跟阻塞(blocking)與非阻塞(non-blocking)有關,詳細的可以去看 Node.js 的官方文件有講到這一塊:Overview of Blocking vs Non-Blocking

這邊只簡單介紹:

  • 阻塞(blocking)=> 代表執行時程式會卡在那一行,直到有結果。
  • 非阻塞(non-blocking)=> 代表執行時不會卡住,沒有看到結果也會繼續執行下去。

很像剛剛在描述同步跟非同步吧?其實在JavaScript當中,可以把兩個劃上等號,剛剛 Node.js 的官方文件有一段是這樣講的:

圖片來源:Node.js 官方文件
這段的意思就是,阻塞的方法會同步的方式執行,而非阻塞的方法會非同步的方式執行,所以我會把兩種當作是同一種東西,注意僅限於JavaScript,其他語言可能不一定,要是全部都一模一樣那幹嘛分兩個不一樣的名稱對吧?

總結

準備要來下結論了,前面有提過到的一次做很多事情,看完上面的文章,相信你已經知道那並不是JavaScript非同步的特色,JavaScript永遠不會一次做很多事情,而實際上是怎麼在JavaScript做到非同步這件事情的,這就是明天要來講的主題,我們明天見啦~

reference

[1] MDN - Introducing asynchronous JavaScript
[2] W3Schools - Asynchronous JavaScript
[3] You Don’t Know JS - Asynchrony
[4] Javascript 非同步 & Event Loop!10 分鐘輕鬆圖解學習!
[5] 你懂 JavaScript 嗎?#22 非同步:現在和以後
[6] 重新認識 JavaScript: Day 26 同步與非同步
[7] 所以說 event loop 到底是什麼玩意兒?| Philip Roberts | JSConf EU
[8] JavaScript 中的同步與非同步(上):先成為 callback 大師吧!
[9] 異步程式設計與事件迴圈