前端基礎

-

你知道非同步嗎?前端新手必學的同步非同步、回調函數 callback 觀念

this.web

前言

對於剛學程式或網頁的新手,很容易把同步和非同步搞混,回調函數的觀念和用法也不簡單,我一開始學習網頁的時候也是搞不懂這些東西,所以今天想用一篇長文來幫助你搞清楚這方面的所有觀念,整篇貼文的架構會如下:

  1. 同步和非同步
  2. 回調函數 (callback) 介紹、用法
  3. 回調地獄 (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 來解決。

希望今天這篇貼文能讓你對非同步有個認識,那今天就這樣,老樣子,下篇貼文見~!

相關系列文章