JavaScript 同步與非同步

一、JavaScript 在做事情時的規則(超重要)

JavaScript 是「一次只能做一件事」的語言

你可以把 JavaScript 想成:

👦 一個只有「一個腦袋、一雙手」的小朋友

這個小朋友:

  • 一次只能做一件事情
  • 做完一件,才會做下一件

這就叫做:

👉 單執行緒(Single Thread)


什麼是「同步」?

同步 = 排隊一個一個來

就像你在洗手:

  1. 先開水龍頭
  2. 洗手
  3. 關水龍頭

前一步沒做完,後一步不能開始

同步的特點

  • 程式會照順序跑
  • 前一行沒跑完,下一行一定不會跑
  • 如果卡住了,後面全部都停住

什麼是「非同步」?

非同步 = 先交代,之後再回來

就像你請媽媽幫你煮泡麵:

  1. 你跟媽媽說:「幫我煮泡麵」
  2. 媽媽去煮
  3. 你先去寫作業
  4. 泡麵好了,媽媽再叫你

👉 你不用站在旁邊等


JavaScript 什麼時候會用非同步?

常見情況:

  • 跟網路要資料(API、AJAX)
  • 倒數計時(setTimeout)

因為這些事情:

  • 需要時間
  • JavaScript 不想傻傻等

二、同步「卡住」的例子

範例一:alert 為什麼很討厭?

1
2
3
4
5
console.log('開始執行');

alert('請點擊確定');

console.log('結束執行');

發生什麼事?

  1. 顯示「開始執行」
  2. 跳出 alert 視窗
  3. 一定要你按「確定」
  4. 才會顯示「結束執行」

為什麼不推薦?

因為:

  • 整個網頁都被卡住
  • 使用者什麼都不能做

👉 實務上幾乎不會用 alert


範例二:for 迴圈為什麼會卡?

1
2
3
4
5
6
7
console.log(1);

for (let i = 0; i < 1000000; i++) {
// 很忙
}

console.log(2);

為什麼一定先跑完 for?

因為 JavaScript 會說:

「我一定要把這個 for 全部跑完,
才能去做下一件事」

所以:

  • 2 一定最後才出現

同步一句話總結

同步就是:一件事情沒做完,後面的事情全部不能開始


三、setTimeout 在做什麼?

很重要的一件事

❗ setTimeout 不是 JavaScript 本身的能力

JavaScript 其實:

  • 不會計時
  • 不會自己等秒數

所以它會:

👉 把事情「交給外面的人幫忙」

這個「外面的人」叫做 Web APIs


範例來看順序

1
2
3
4
5
6
7
8
console.log(1);

setTimeout(() => {
console.log(2);
}, 0);

console.log(3);
console.log(4);

實際輸出

1
2
3
4
1
3
4
2

為什麼不是 1 2 3 4?

發生流程像這樣:

  1. JavaScript 先把「同步的事情」做完
    👉 印出 1、3、4
  2. setTimeout 交給 Web APIs
  3. 等全部事情都空了
  4. 才回來印 2

👉 不是 0 秒就馬上跑


一句話記住

所有非同步的事情,一定最後才執行


四、為什麼串 API 會出問題?

想像你跟後端要資料:

  • 有時很快
  • 有時很慢
  • 有時網路卡住

👉 你不能保證什麼時候拿到


❌ 錯誤寫法

1
2
const data = fetch('/api/users');
console.log(data);

為什麼錯?

因為:

  • fetch 是「去要資料」
  • 資料還沒回來
  • 你就急著印

所以你只會看到:

👉 Promise(等待中)


五、Promise 是什麼?

Promise 就像一張:

📄「我保證之後會給你答案」的紙

但現在:

  • 還不知道結果
  • 只能等

Promise 三種狀態(很重要)

  1. ⏳ 等待中(Pending)
  2. ✅ 成功(Fulfilled)
  3. ❌ 失敗(Rejected)

Promise 範例(用白話想)

1
2
3
4
5
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('資料取得成功');
}, 2000);
});

意思是:

「2 秒後,我會告訴你結果」


六、async / await 是什麼?

超簡單說法

讓你用「看起來像同步」的方式,寫非同步程式


範例

1
2
3
4
5
async function fetchData() {
const response = await fetch('/api/users');
const data = await response.json();
console.log(data);
}

await 在幹嘛?

意思是:

「這一行我先等一下,
等結果回來再繼續」

但注意:

  • JavaScript 不是真的卡住
  • 它只是「先去做別的事」

為什麼大家愛用?

因為:

  1. 好讀
  2. 好寫
  3. 好維護
  4. 不用 .then() 一直接

七、為什麼會看到 Promise <pending>?

1
2
const data = promiseSetTimeout(1);
console.log(data);

原因超簡單

因為你在說:

「我現在就要答案」

但實際情況是:

「答案還沒準備好」

所以只能看到:

👉 Promise { <pending> }


八、正確寫法(一定要 await)

1
2
3
4
async function fn() {
const data = await promiseSetTimeout(1);
console.log(data);
}

意思是:

「我願意等你一下」


九、async / await 常見錯誤(新手必踩)

❌ 忘記加 async

1
2
3
function fn() {
const data = await promiseSetTimeout(1);
}

👉 不能用 await


❌ 忘記加 await

1
2
3
async function fn() {
const data = promiseSetTimeout(1);
}

👉 還是在等


❌ await 不能等 setTimeout

1
await setTimeout(...)

因為:

  • setTimeout 不是 Promise

十、為什麼函式宣告方式很重要?

1
2
3
fn(); // ❌

const fn = async () => {};

因為:

  • 箭頭函式不會被提前準備好

正確方式:

1
async function fn() {}

十一、IIFE:立刻執行的小技巧

1
2
3
(async () => {
const data = await promiseSetTimeout(1);
})();

意思是:

「我不想取名字,我只想現在跑」


十二、為什麼一定要 try / catch?

因為:

  • async 裡面只要出錯
  • 後面全部不跑
1
2
3
4
5
try {
await 做事情();
} catch (error) {
處理錯誤();
}

👉 好習慣,一定要寫


十三、Axios 是什麼?

Axios 是:

  • 幫你跟後端要資料
  • 已經包成 Promise
  • 非常好搭配 async / await

十四、前後端在幹嘛?(超白話)

  • 前端:畫畫面、按按鈕
  • 後端:算資料、存資料

流程像:

1
你 → 前端 → 後端 → 前端 → 顯示給你看