前端基礎
-你知道非同步嗎?前端新手必學的同步非同步、回調函數 callback 觀念
this.web
前言
對於剛學程式或網頁的新手,很容易把同步和非同步搞混,回調函數的觀念和用法也不簡單,我一開始學習網頁的時候也是搞不懂這些東西,所以今天想用一篇長文來幫助你搞清楚這方面的所有觀念,整篇貼文的架構會如下:
- 同步和非同步
- 回調函數 (callback) 介紹、用法
- 回調地獄 (callback hell) 的問題
那我們就開始吧!
非同步、同步是什麼?
同步跟非同步的觀念其實並不是只存在於前端,而是整個電腦科學的領域都會接觸到的東西。它其實也不難理解。
- 同步(Synchronous、sync):做完一件事情後接著做另一件事情
- 非同步(Asynchronous、async):同時做多件事情
用一張圖來舉例:
舉例來說,你一天的行程可能是,吃早餐 - 工作 - 吃午餐 - 開會 - 閱讀 - 吃晚餐 - 運動,過程中你沒辦法做其他事情,例如邊開會邊閱讀,這就是同步,一件事情做完才能做另一件事情。
而非同步就像是你請兩個人幫你工作跟開會,而你可以專心閱讀,就會變成
吃早餐 - 工作、開會、讀書 (同時進行) - 吃午餐 - 其他事 - 吃晚餐 - 運動
有沒有發現工作開會讀書一個早上就完成了,效率很高,這就是非同步的優點。
同步和非同步是指資料的狀態
在接觸這個名詞有點容易搞混,以為同步是同時進行很多事情,我也搞混過
不過這裡的同步是指狀態或資料上面的同步。
一樣行程的例子,你閱讀完後隔天可以直接把學到的知識用工作上,因為狀態(資料)同步了。但若是非同步的狀況下,你所學到的知識無法即時傳遞給另外兩個人,不可能說你學到了,另外兩個人也會了,這就是資料非同步。
一開始可能需要花點時間適應,不過知道觀念後就不難記了,接著讓我們來看看同步和非同步在程式中的樣子。
同步和非同步在程式中的樣子
其實同步在程式的世界裡很好懂,來看看下面的例子:
function double(value) {
console.log(value * 2);
}
let x = 3;
double(x);
console.log(x + 1);
// 6
// 4
同步就是每行程式碼都會依照順序執行,先宣告 x 後,執行 double(),再打印 x + 1。
而非同步就會是這樣:
function double(value) {
setTimeout(() => console.log(value * 2), 1000);
}
let x = 3;
double(x);
console.log(x + 1);
// 4
// 6
就算 double(x) 再 x + 1 前面還是會先打印出 4,一秒後才打印出 6。
💡 在 js 裡,我們可以使用 setTimeout 來模擬非同步,因為非同步完成的時間不一定,所以我們就用 1 秒來模擬非同步的處理時間。
補充:非同步一定在同步執行完後才會執行,就算定時器設定 0 毫秒也一樣
甚麼情況要用到非同步
我們經常需要去後端提供的接口來取得資料(也就是所謂的 API串接),如果我們等到資料全部取得完才顯示頁面,就會拖太久讓體驗不好,這時就可以用到非同步的觀念。
function getSomeData(url) {
// 獲取 data
}
getSomeData(url)
// other operation
像上面的程式碼,getSomeData(url) 是去後端提供的網址取得資料,同時我們也可以處理其他運算(other operation)。
這裡可以先不管是如何取得的,但你好奇的話,在前端通常會用 Promise、async/await 也就是等等要講的來處理非同步)
如何知道非同步處理完了?回調函數
因為我們無法確切知道非同步的處理時間,網路、電腦速度都會影響,所以我們會用回調函數 callback。
function getSomeData(url, callback) {
// 獲取 data
// 獲取完 data 後,執行 callback 回調函數
callback(yourData);
}
getSomeData(url1, (data1) => { // 👈 callback function
// 對 data1 做處理 ...
getSomeData(data1.url); // 利用 data1 取得其他資料
})
// other operation
例如上面,送回來的資料又有一個網址,而我們又剛好需要去這個網址拿其它資料,就可以利用回調函數來實現。
回調函數的問題 - 回調地獄 & 規範
看起來很美好的回調函數其實有個大問題,若我們又要去拿第三個資料,甚至第四、五個,就會變成下面這個樣子 👇
function getSomeData(url, callback) {
// 獲取 data
callback(yourData);
}
getSomeData(url1, (data1) => {
getSomeData(data1.url, (data2) => {
getSomeData(data2.url, (data3) => {
getSomeData(data3.url, (data4) => {
...
})
});
})
})
// other operation
不斷用回調函數,會讓程式碼變得難看又難維護,這也是所謂的回調地獄(callback hell)。除了回調地獄,回調函數也沒有一個規範,每個人的寫法都不一樣,造成使用和閱讀上的困難。
為了解決這個問題,JS 增加了 Promise 和 async/await 來幫助我們,Pormise 和 async/await 的觀念我們放到下篇貼文來聊聊~
小結
今天介紹了同步非同步以及回調函數這兩個非常重要的觀念,基本上我們在串接 API 的時候都是用非同步的方式進行,串接 API 的貼文可以參考這一篇。
而早期處理非同步的方式是使用回調函數,但是回調函數也產生了相對的問題,所以後來出現了 Promise 來解決。
希望今天這篇貼文能讓你對非同步有個認識,那今天就這樣,老樣子,下篇貼文見~!