# Explain the difference between synchronous and asynchronous functions.
::: info
:bookmark:同步函數會阻塞 (blocking) 。
同步函式中,每個 statements 會在下一個 statements 執行前完成。這種情況下,程式會按照順序進行精確評估,如果其中一個 statements 執行花了很長的時間,程式就會停住,直到執行完成才會繼續跑。
:bookmark:非同步函數不會阻塞 (non-blocking) 。
非同步函式通常接收 callback 作為參數,並且在非同步函式呼叫後立即執行下一行。 callback 僅會在非同步程式操作完成和 call stack 清空時被呼叫。
:bookmark: 從 web server 載入資料或是查詢 database 之類的繁重操作,都應該以非同步完成,以讓 main thread 可以持續執行其他的操作。(因為以瀏覽器的角度來看,同步的操作會使得 UI 凍結(freeze))
:::
[阻塞 blocking( Promise, Async / Await篇有提到 )](https://hackmd.io/30UT3xH5TyOFHUJRuLQ9Uw#%E3%80%90%E3%80%91Blocking-%E9%98%BB%E5%A1%9E)
**main thread 主執行緒**:在瀏覽器裡面,負責執行 JavaScript 的叫做 main thread,負責處理跟畫面渲染相關的也是 main thread。
---
## JavaScript 本身是同步的
JavaScript 基本上是一個同步,且跑在單一執行緒 (Single-Thread) 的程式語言,也就是在同一時間只能執行一個操作。
### 那為什麼可以執行非同步事件呢?
因為當在執行 Javscript 的時候,Javascript 是在瀏覽器內執行的。
瀏覽器這個執行環境 (runtime) 還提供了很多 WebAPI (ex: document、XMLHttpRequest、setTimeout) 給我們使用,他們不在 V8 引擎中,也是我們無法取得的內容,所以我們只能呼叫這些功能去執行他。
當瀏覽器知道你要呼叫他們來用的時候,就可以和你的程式碼同時一起執行 ,也不會影響到你的 JS 主程式。
**==所以在瀏覽器內,只有 JavaScript 引擎本身是同步的,而 Javascript 引擎可以跟 WebAPI 溝通,達到非同步的事件處理。==**
## 生活中的同步與非同步
### 想像你去咖啡店買拿鐵和黑咖啡:
**==同步 Synchronous==**
1. 店員在收銀機上輸入你的點餐內容
2. 店員請 A 同事開始準備拿鐵
3. A 同事準備好拿鐵,店員轉交給你
4. 店員請 B 同事開始準備黑咖啡
5. B 同事準備好黑咖啡,店員交給你
6. 店員幫你結帳
**==非同步 Asynchronous==**
1. 店員在收銀機上輸入你的點餐內容
2. 店員請 A 同事準備拿鐵、B 同事準備黑咖啡。B同事先完成了,剛好店員幫你結完帳沒事,所以先把黑咖啡拿給你。
3. 拿鐵製作包含較多步驟,花費比較多時間,等 A 同事完成後,店員剛好沒事,所以把拿鐵也遞給你。
從咖啡店的例子複習專有名詞:
1. 同步的例子裡,店員一次就只能做一件事情 ==(Single thread 單執行緒)==; <br/>而在非同步的例子中,將製作咖啡的事情委派給其他同事處理,讓自己可以繼續幫你結帳,來提高效率。
**JavaScript 是 Single thread,一次只能做一件事情。**
2. 非同步的例子中,店員將製作咖啡的任務發出去後,其實不知道哪一個任務會先完成,但卻可以繼續自己結帳的流程,不會因為同事 A、B 還在製作咖啡,就不能接下去動作 ==(不會阻塞 non-blocking)== 。<br>JavaScript 一次只能做一件事,但藉由瀏覽器或 Node 提供的 API 協助,在背後處理這些事件(想像成同事在背後製作咖啡),得以在等待製作的同時,不會 ==阻塞(blocking)== 到下一件事的執行(想像成店員可以繼續結帳流程)。
<br/>
## 瀏覽器的同步與非同步
### 假設我們今天在 JavaScript 有一個函式叫做 getAPIResponse,能串接後端的 API 拿取資料。
### 同步的寫法:
``` javascript=
// JavaScript 執行這行會一直等到回傳資料後,才會繼續執行下一行程式碼。
const response = getAPIResponse()
console.log(response)
```
假如今天要等到 10 秒後,API Server 才會回傳資料,這段期間等於說讓執行 JavaScript 的執行緒(thread)凍結。
在瀏覽器裡面,負責執行 JavaScript 的叫做 main thread,負責處理跟畫面渲染相關的也是 main thread。
如果這個 thread 凍結 10 秒,會造成無法點擊畫面,因為瀏覽器沒有資源去處理其他事情。
而瀏覽器裡執行 JavaScript 的 main thread 同時也負責畫面的 render,因此非同步變得很重要。否則等待的時候畫面會凍結,就像電腦當機。
### 將程式改成非同步,使用 callback function 來接收結果:
``` javascript=
// 寫法一:額外宣告函式
function handleResponst() {
console.log(response)
}
getAPIResponse(handleResponst)
// 寫法二:匿名函式
getAPIResponse(function(err, response) {
console.log(response)
})
// 寫法三:利用 ES6 箭頭函式簡化過後
getAPIResponse((err, response) => {
console.log(response)
})
```
以 AJAX 來看:
``` javascript=
var request = new XMLHttpRequest()
request.open('GET', 'https://jsonplaceholder.typicode.com/users/1', true);
request.onload = function() {
if (this.readyState == 4 && this.status == 200) {
console.log(this.response)
}
}
request.send();
```
這邊的 callback function 就是 request.onload = 後面的那個函式,
代表「當 response 回來時,請執行這個函式」。
:::success
**常見的一些非同步 callback function**
複習 callback function,它的意思是「當某事發生的時候,請利用這個 function 通知我」
``` javascript=
// 例子1
const btn = document.querySelector('.btn_alert')
// 當有人點擊 .btn_alert 這個按鈕時,
// 請利用這個 function(handleClick)通知我
btn.addEventListener('click', handleClick)
// handleClick 就是 callback function
function handleClick() {
alert('click!')
}
// 例子2
// 當網頁載入完成發生時,請利用這個 function(匿名函式)通知我
window.onload = function() {
alert('load!')
}
// 例子3
// 當過了兩秒,請利用這個 function(tick)通知我
setTimeout(tick, 2000)
function tick() {
alert('時間到!')
}
```
:zap: **然而要注意的是,不是所有的回呼都是非同步的——有些是跑在同步上。**
比如說當我們使用forEach() 在陣列裡面用迴圈來遍歷每一個項目:
``` javasrcipt=
const gods = ['Apollo', 'Artemis', 'Ares', 'Zeus'];
gods.forEach(function (eachName, index){
console.log(index + '. ' + eachName);
});
```
:::
更多的非同步技術請參考之前的:[Promise, Async / Await 篇](https://hackmd.io/30UT3xH5TyOFHUJRuLQ9Uw#%E3%80%90%E3%80%91Blocking-%E9%98%BB%E5%A1%9E)
## 非同步小測驗
### 1. 執行順序
``` javascript=
a(function() {
console.log('a')
})
console.log('hello')
// 請問:最後的輸出順序為何?是先 hello 再 a,還是先 a 再 hello
// 還是不一定?
```
:::spoiler answer
答案是不一定。
因為沒有說 a 是同步還是非同步的。
a 可以這樣實作:
``` javascript=
function a(fn) {
fn() // 同步執行 fn
}
a(function() {
console.log('a')
})
console.log('hello')
```
輸出就會是 a 然後 hello。
a 也可以這樣實作:
``` javascript=
function a(fn) {
setTimeout(fn, 0) // 非同步執行 fn
}
a(function() {
console.log('a')
})
console.log('hello')
```
輸出就是 hello 然後才 a。
:::
### 2. 慢慢等
期待:ajax 的 response 回來之前會不斷印出 waiting,直到接收到 response 才停止。
``` javascript=
let gotResponse = false
getAPIResponse('/check', () => {
gotResponse = true
console.log('Received response!')
})
while(!gotResponse) {
console.log('Waiting...')
}
```
請問:以上寫法可以滿足期待嗎?如果不行,請詳述原因。
:::spoiler answer
答案是不行。
還記得 event loop 的條件嗎?「當 call stack 為空,才把 callback 丟到 call stack」。
``` javascript=
while(!gotResponse) {
console.log('Waiting...')
}
```
這一段程式碼會不斷執行,成為一個無窮迴圈。所以 call stack 永遠都有東西,一直被佔用,callback queue 裡面的東西根本丟不進 call stack。
因此原本的程式碼無論有沒有拿到 response,都只會一直印出 waiting。
:::
### 3. 詭異的計時器
小明被主管指派要去解一個 bug,在公司的程式碼裡面找到了這一段:
``` javascript=
setTimeout(() => {
alert('Welcome!')
}, 1000)
// 後面還有其他程式碼,這邊先略過
```
這個 bug 是什麼呢?
就是這個計時器明明指定說 1 秒之後要跳出訊息,可是執行這整段程式碼(注意,底下還有其他程式碼,只是上面先略過而已)以後,alert 卻在 2 秒以後才跳出來。
請問:這有可能發生嗎?無論你覺得可能或不可能,都請試著解釋原因。
:::spoiler answer
答案是有可能。
WebAPI 會在一秒之後把 callback 丟到 callback queue,那為什麼兩秒之後才會執行呢?因為這一秒 call stack 被佔用了。
只要 setTimeout 底下的程式碼做了很多事情並佔用了一秒鐘,callback 就會在一秒之後才被丟到 call stack 去,例如說:
``` javascript=
setTimeout(() => {
alert('Welcome!')
}, 1000)
// 底下這段程式碼會在 call stack 佔用一秒鐘
const end = +new Date() + 1000
while(end > new Date()){
}
```
所以 setTimeout 只能保證「至少」會在 1 秒後執行,但不能保證 1 秒的時候一定執行。
:::
---
[JavaScript 中的同步與非同步(上):先成為 callback 大師吧!](https://blog.huli.tw/2019/10/04/javascript-async-sync-and-callback/)