作為一種單執行緒語言,JavaScript 一直依賴非同步程式設計來處理耗時的任務而不阻塞程式碼執行。多年來,JavaScript 中處理非同步性的方法已經發生了顯著的發展,變得更易於閱讀、更容易管理、更容易推理。讓我帶您了解非同步 JavaScript 的歷史,從回呼到 Promise,再到 async/await。
在 JavaScript 的早期,在廣泛採用回呼之前,大多數 JavaScript 程式碼都是同步編寫的。同步程式碼意味著每個操作都以阻塞方式依序執行。當遇到長時間運行的操作時,整個腳本的執行將暫停,直到該操作完成。
想像一下,您在火車站售票櫃檯,只有一名售票員。您申請門票,售票員開始處理您的要求。在同步模型中,您必須在櫃檯等待,直到售票員處理完您的請求並將門票交給您。在此期間,無法為其他人提供服務,整個售票櫃檯都被封鎖了。
這是同步 JavaScript 程式碼的範例:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
在此程式碼中,console.log 語句將依序執行,長時間執行的動作(for 迴圈)將阻止腳本的執行,直到完成。 「操作後」訊息僅在循環結束後才會記錄。
雖然同步程式碼易於理解和推理,但它會帶來一些問題,特別是在處理耗時的操作時:
為了克服同步程式碼的限制並提供更好的使用者體驗,引入了非同步程式設計技術。非同步程式允許在背景執行長時間運行的操作,而不會阻止其餘程式碼的執行,這就是回呼的引入方式。
回呼是處理非同步操作的主要方式。回調只是作為參數傳遞給另一個函數的函數,稍後在非同步操作完成後執行。
想像一下您想買一張火車票。您前往火車站的售票櫃檯索取特定目的地的車票。售票員接受您的請求並要求您稍候,他們會檢查火車上的空座情況。您向他們提供您的聯絡資訊並在等候區等候。一旦售票員處理了您的請求並且有空位,他們就會叫出您的名字,讓您知道您的票已準備好領取。在這個類比中,您的聯絡資訊就是回調 - 售票員在非同步任務(檢查座位可用性和出票)完成時通知您的一種方式。
以下是 JavaScript 中的回呼的類比:
在回呼方法中,您提供一個函數(回呼),非同步操作完成後將呼叫該函數。非同步函數執行其任務,然後使用結果或錯誤呼叫回調,從而允許您的程式碼處理非同步操作的結果。
以下是在 Node.js 中使用回呼進行 API 呼叫的範例:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
在此範例中,我們有一個模擬 API 呼叫的 fetchData 函數。它接受一個 url 參數和一個回調函數作為參數。在函數內部,我們使用 setTimeout 來模擬呼叫回呼函數之前 1000 毫秒(1 秒)的延遲。
回呼函數遵循常見約定,接受錯誤為第一個參數 (err),接受資料作為第二個參數 (data)。在此範例中,我們透過將錯誤設為 null 並提供範例資料物件來模擬成功的 API 呼叫。
要使用 fetchData 函數,我們使用 URL 和回呼函數來呼叫它。在回調函數中,我們首先透過檢查 err 參數來檢查是否發生錯誤。如果有錯誤,我們使用 console.error 將其記錄到控制台並返回以停止進一步執行。
如果沒有發生錯誤,我們使用console.log將接收到的資料記錄到控制台。
當您執行此程式碼時,它將模擬非同步 API 呼叫。延遲1秒後,將呼叫回呼函數,並將結果記錄到控制台:
{ id: 1, name: 'John Doe' }
此範例示範如何使用回呼來處理非同步 API 呼叫。回呼函數會作為參數傳遞給非同步函數 (fetchData),並在非同步操作完成後呼叫它,無論是出現錯誤還是結果資料。
雖然回調完成了工作,但它們有幾個缺點:
為了解決回呼所帶來的挑戰,ES6 (ECMAScript 2015) 中引入了 Promise。 Promise 代表非同步操作的最終完成或失敗,並允許您將操作連結在一起。
將承諾想像成一張火車票。當您購買火車票時,該車票代表鐵路公司對您能夠登上火車並到達目的地的承諾。車票上包含有關火車的信息,例如出發時間、路線和座位號。拿到車票後,就可以等待火車到車站,準備上車後,就可以憑車票上車了。
在這個比喻中,火車票就是承諾。它代表非同步操作(火車旅程)的最終完成。您保留票(承諾對象),直到火車準備好(非同步操作完成)。一旦承諾得到解決(火車到車站),您就可以使用車票登上火車(獲取解決值)。
以下是 JavaScript 中的 Promise 的類比:
Promise 提供了一種結構化的方式來處理非同步操作,讓您可以將多個操作連結在一起並以更易於管理的方式處理錯誤,就像火車票如何幫助您組織和管理火車旅程一樣。
這是使用 Promise 進行 API 呼叫的範例:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
在此程式碼中,fetchData 函數傳回一個承諾。 Promise 建構函式採用一個接受兩個參數的函式:resolve 和reject。這些函數用於控制 Promise 的狀態。
在 Promise 建構函數中,我們使用 setTimeout 模擬 API 調用,就像前面的範例一樣。然而,我們沒有呼叫回調函數,而是使用resolve和reject函數來處理非同步結果。
如果發生錯誤(在本例中,我們透過檢查錯誤變數來模擬錯誤),我們會呼叫帶有錯誤的拒絕函數,表示該 Promise 應該被拒絕。
如果沒有發生錯誤,我們將使用資料呼叫resolve函數,表示應使用接收到的資料來解析promise。
要使用 fetchData 函數,我們將 .then() 和 .catch() 方法連結到函數呼叫。 .then() 方法用於處理 Promise 的解析值,而 .catch() 方法用於處理可能發生的任何錯誤。
如果 Promise 成功解析,則會使用解析後的資料呼叫 .then() 方法,並使用 console.log 將其記錄到控制台。
如果發生錯誤且 Promise 被拒絕,則會使用 err 物件呼叫 .catch() 方法,然後我們使用 console.error 將其記錄到控制台。
與回呼相比,使用 Promise 提供了一種更結構化和可讀的方式來處理非同步操作。 Promise 可讓您使用 .then() 將多個非同步操作連結在一起,並使用 .catch() 以更集中的方式處理錯誤。
Promise 透過多種方式改善了回調:
但是,Promise 仍然有一些限制。連結多個 Promise 仍然可能導致深度嵌套的程式碼,並且語法並不像應有的那麼乾淨。
ES8 (ECMAScript 2017) 中引入的 Async/await 建構在 Promise 之上,並提供了一種看起來更同步的方式來編寫非同步程式碼。
使用 async/await,您可以編寫外觀和行為類似於同步程式碼的非同步程式碼。這就像有一個私人助理幫你售票櫃檯。你只需等待你的助手帶著票回來,一旦他們回來,你就可以繼續你的旅程了。
這是使用 async/await 進行 API 呼叫的範例:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
在此程式碼中,我們有一個名為 fetchData 的非同步函數,它採用表示 API 端點的 url 參數。在函數內部,我們使用 try/catch 區塊來處理 API 請求期間可能發生的任何錯誤。
我們在 fetch 函數之前使用 wait 關鍵字來暫停執行,直到 fetch 傳回的 Promise 得到解析。這表示函數將等到 API 請求完成後再繼續下一行。
收到回應後,我們使用await response.json() 將回應正文解析為JSON。這也是一個非同步操作,所以我們使用await來等待解析完成。
如果API請求和JSON解析成功,則從fetchData函數傳回資料。
如果 API 請求或 JSON 解析過程中發生任何錯誤,都會被 catch 區塊捕獲。我們使用 console.error 將錯誤記錄到控制台,並使用 throw err 重新拋出錯誤,將其傳播給呼叫者。
為了使用 fetchData 函數,我們有一個名為 main 的非同步函數。在 main 中,我們指定要從中取得資料的 API 端點的 url。
我們使用await fetchData(url)來呼叫fetchData函數並等待它回傳資料。如果 API 請求成功,我們會將收到的資料記錄到控制台。
如果 API 請求程序中發生任何錯誤,則會被 main 函數中的 catch 區塊捕獲。我們使用 console.error 將錯誤記錄到控制台。
最後我們呼叫main函數開始執行程式。
當您執行此程式碼時,它將使用 fetch 函數向指定的 URL 發出非同步 API 請求。如果請求成功,接收到的資料將記錄到控制台。如果發生錯誤,也會被捕獲並記錄。
將 async/await 與 fetch 函數結合使用,提供了一種乾淨且可讀的方式來處理非同步 API 請求。它允許您編寫外觀和行為類似於同步程式碼的非同步程式碼,使其更易於理解和維護。
總之,非同步 JavaScript 的演變,從回呼到 Promise 再到 async/await,一直是朝著更具可讀性、可管理性和可維護性的非同步程式碼邁進的旅程。每一步都建立在前一步的基礎上,解決了限制並改善了開發人員體驗。
如今,async/await 已被廣泛使用,並已成為 JavaScript 中處理非同步操作的首選方式。它允許開發人員編寫乾淨、簡潔且易於理解的非同步程式碼,使其成為每個 JavaScript 開發人員工具箱中的寶貴工具。
以上是非同步 JavaScript - 從回呼到非同步等待的旅程的詳細內容。更多資訊請關注PHP中文網其他相關文章!