Javascript回撥非同步操作示例教程

banq發表於2024-04-13

有沒有想過網站如何在不堵塞所有內容的情況下獲取資料?這就是非同步操作的魔力!

回撥是處理這些幕後任務的經典方法。想象一下,您告訴朋友(函式)獲取某些東西(資料)。當你的朋友購物(操作執行)時,你(主程式)可以繼續做事。一旦他們帶回專案(資料),您的朋友就會告訴您(回撥函式),您就可以使用它!

回撥是一個簡單的概念,但存在更新的方法來保持程式碼整潔。儘管如此,理解回撥仍然是非同步世界中偉大的第一步!

傳統同步與非同步操作
想象一下,您有一個名為 的函式makeCoffeeSync可以完美地衝泡一杯咖啡。但是,它是同步的,這意味著程式會等待整個過程完成後再繼續。它可能如下所示:

function makeCoffeeSync() {
  <font>// Simulate brewing time (waiting 5 seconds)<i>
  for (let i = 0; i < 50000000; i++) {}
// This loop wastes time to simulate brewing<i>
  console.log(
"Coffee is ready!");
}

makeCoffeeSync();
// Call the function<i>

console.log(
"While the coffee brews, I can..."); // This line won't be reached until coffee is done!<i>


在這種情況下

  • 程式會研磨咖啡豆
  • 加熱水並沖泡咖啡,
  • 所有這些都在該makeCoffeeSync函式內進行。
  • 在咖啡喝完之前,甚至不會到達該佇列,因為程式處於等待狀態(for迴圈模擬沖泡時間)。

這對於網站來說並不理想;使用者會看到等待的螢幕,直到咖啡準備好!

非同步救世主:多工處理的咖啡
現在,讓我們看看非同步操作如何在回撥的幫助下以不同的方式處理咖啡製作:

function makeCoffeeAsync(callback) {
  <font>// Simulate brewing time (still 5 seconds)<i>
  setTimeout(() => {
    console.log(
"Coffee is ready!");
    callback();
// Call the callback function once brewing is done<i>
  }, 5000);
}

makeCoffeeAsync(function() {
  console.log(
"... I can check my email while the coffee brews!");
});

console.log(
"I can also start reading the news..."); // This line can be executed immediately!<i>

神奇之處就在這裡:

  • makeCoffeeAsync 將一個回撥函式作為引數。該回撥函式會告訴 makeCoffeeAsync 函式在咖啡煮好後要執行哪些程式碼。
  • setTimeout 模擬衝煮時間(5 秒)。在 setTimeout 函式中,我們呼叫回撥函式(console.log("咖啡煮好了!"))來提示咖啡已經煮好。
  • 一旦咖啡煮好(setTimeout 延遲後),我們就會執行所提供的回撥函式(console.log("......我可以在煮咖啡時檢視電子郵件了!")。
  • 重要的是,console.log("我還可以開始閱讀新聞......")行可以立即執行,因為程式不會等待咖啡煮好。

這種非同步方法允許程式在後臺沖泡咖啡時保持平穩執行。回撥功能可確保您在咖啡煮好後收到通知並採取行動(如檢視電子郵件)。這使得使用者體驗更加靈敏!

回撥優點
把非同步函式想象成送貨員。你(主程式)不能等著他們送包裹(非同步操作的結果)。但你可以給他們下達指示(回撥函式),告訴他們拿到包裹(操作完成)後該怎麼做。

傳遞指令:

  • 非同步函式:假設您有一個名為 fetchDataFromServerAsync 的函式,用於從伺服器獲取資料。這個函式是非同步的,因為它需要時間與伺服器通訊。
  • 打包指令:呼叫此函式時,需要提供另一個函式(回撥函式)作為引數。回撥函式包含資料檢索後的操作指令。

傳送過程:
  • 非同步函式啟動:fetchDataFromServerAsync 函式開始與伺服器聯絡並獲取資料。
  • 程式無需等待:當 fetchDataFromServerAsync 忙碌時,主程式無需等待。它可以繼續執行其他程式碼。
  • 傳送完成!(操作完成):一旦從伺服器檢索到資料,fetchDataFromServerAsync 函式就完成了非同步操作。
  • 移交資料包(回撥呼叫):現在,fetchDataFromServerAsync 會記住你之前提供的回撥函式。它會呼叫該回撥函式,並將檢索到的資料(資料包)作為引數傳遞。

function fetchDataFromServerAsync(callback) {
  <font>// Simulate fetching data from a server (wait 2 seconds)<i>
  setTimeout(() => {
    const catData =
"Here are some cat videos!";  // This is the retrieved data (package)<i>
    callback(catData);  
// Call the callback function and pass the data<i>
  }, 2000);
}

// The callback function to handle the retrieved data<i>
function processCatData(data) {
  console.log(
"Great! I can now show these cat videos to the user:", data);
}

// Calling the asynchronous function and providing the callback<i>
fetchDataFromServerAsync(processCatData);

console.log(
"While we wait for cat videos, I can...");  // This can be executed immediately<i>

在這個例子中

  • fetchDataFromServerAsync 是非同步函式,用於模擬從伺服器獲取資料(貓影片)。
  • processCatData 是回撥函式,用於指定如何處理獲取的資料(貓咪影片)--在本例中,將其記錄到控制檯。
  • 回撥函式(processCatData)作為引數傳遞給 fetchDataFromServerAsync。
  • 主程式可以繼續執行(console.log("當我們等待貓影片時,我可以...")),因為它不需要等待資料被獲取。
  • 一旦 fetchDataFromServerAsync 完成資料(貓咪影片)的獲取,它就會呼叫回撥函式(processCatData)並將資料移交給主程式。
  • 這就是非同步操作回撥的精髓。回撥函式可確保您在非同步任務完成時對結果採取行動,從而保持程式的響應速度。

回撥的優點和缺點
回撥為處理非同步操作提供了一些好處:

  • 簡單性: 將函式作為引數傳遞的基本概念相對容易掌握,特別是對於初學者來說。
  • 靈活性: 回撥可與各種非同步任務一起使用,提供了一種通用的方法。

然而,隨著應用程式複雜性的增加,回撥可能會帶來一些挑戰:
  • 回撥地獄: 當您開始在彼此之間巢狀多個回撥時(就像送貨員指令的連鎖反應),程式碼可能會變得難以閱讀和維護。想象一下給出一長串具有複雜依賴關係的指令 - 它可能會變得混亂!這種錯綜複雜的結構通常被稱為“回撥地獄”。
  • 可讀性: 過多的巢狀會使邏輯流程難以理解。可能不清楚哪個回撥函式負責它們的互動內容以及互動方式。
  • 錯誤處理: 處理回撥鏈中的錯誤可能很麻煩。您需要在回撥鏈的每個級別檢查錯誤,從而使程式碼變得冗長且容易出錯。

這是一個類比:想象一下,向多個傳遞包(回撥)的朋友提供一長串具有複雜依賴關係的指令。如果傳遞出現問題,跟蹤誰在做什麼以及會發生什麼可能會令人困惑。

雖然回撥發揮著歷史作用,但現代 JavaScript 提供了 Promises 和 Async/Await 等替代方法來解決這些可讀性和可維護性問題。然而,理解回撥為非同步程式設計概念奠定了堅實的基礎。

超越回撥:Promise 和 Async/Await 實現更簡潔的非同步
雖然回撥是一個基本概念,但在複雜的場景中它們可能會變得很麻煩。這是一個現實世界的例子:

1 想象一個電子商務網站:

  1. 回撥連鎖反應: 使用者單擊“新增到購物車”。這會觸發回撥以更新購物車顯示。但是等等,購物車可能會根據使用者的忠誠度計劃應用折扣!這需要另一個非同步呼叫來獲取使用者資料,然後是一個回撥來計算折扣,以及另一個回撥來更新購物車顯示的最終價格。這種回撥的連鎖反應很快就會變得難以管理和除錯。

2 Promise 和 Async/Await 的救援:
現代 JavaScript 為處理非同步操作提供了更清晰的替代方案:

  • Promise:  Promise 為非同步任務提供了一種更加結構化的方法。它們代表非同步操作的最終完成(或失敗),並提供一種以更可讀的方式將操作連結在一起的方法。
  • Async/Await:  Async/Await 構建在 Promises 之上,允許您以類似於同步程式碼的方式編寫非同步程式碼。它使用 async and await 關鍵字使非同步程式碼看起來更加同步,從而提高可讀性。

3 Promise 和 Async/Await 的好處:

  • 提高可讀性: 與回撥地獄相比,基於 Promise 的程式碼或使用 Async/Await 的程式碼通常更易於閱讀和維護。邏輯流程更加清晰,錯誤處理變得更加易於管理。
  • 更好的錯誤處理:  Promise 允許集中錯誤處理,從而更容易捕獲和處理非同步操作中的錯誤。
  • 更簡潔的程式碼: 總體而言,這些方法可以生成更簡潔、更易於維護的程式碼,尤其是在複雜的非同步場景中。

雖然回撥佔有一席之地,但 Promises 和 Async/Await 提供了一種更強大、更高效的方法來處理現代 Web 開發中的非同步操作。透過了解回撥及其限制,您可以瞭解這些新技術在編寫乾淨且可維護的非同步程式碼方面的優勢。


 

相關文章