當我第一次遇到非同步 JavaScript 時,我在回調方面遇到了困難,並且不知道 Promises 在幕後是如何運作的。隨著時間的推移,對 Promise 和 async/await 的了解改變了我的編碼方法,使其更易於管理。在本部落格中,我們將逐步探索這些非同步模式,揭示它們如何簡化您的開發流程並使您的程式碼更乾淨、更有效率。讓我們一起深入探討並揭開這些概念!
學習非同步 JavaScript 對於現代 Web 開發至關重要。它允許您有效地處理 API 請求等任務,使您的應用程式保持快速回應。掌握非同步技術(例如 Promises 和 async/await)不僅對於建立可擴展的應用程式至關重要,而且對於在 JavaScript 工作面試中取得成功也至關重要,而理解這些概念通常是重點。透過掌握非同步 JavaScript,您將提高編碼技能並更好地為現實世界的挑戰做好準備。
JavaScript 中的非同步模式是用於處理需要時間的任務的技術,例如從伺服器獲取數據,而不凍結應用程式。最初,開發人員使用回調來管理這些任務,但這種方法通常會導致程式碼複雜且難以閱讀,稱為「回調地獄」。為了簡化這一點,引入了 Promise,透過連結操作和更優雅地處理錯誤,提供了一種更乾淨的方式來處理非同步操作。 async/await 繼續發展,它允許您編寫看起來和行為更像同步程式碼的非同步程式碼,從而更易於閱讀和維護。這些模式對於建立高效、反應迅速的應用程式至關重要,也是現代 JavaScript 開發的基礎。我們將在本部落格中更詳細地探討這些概念。
回呼 是作為參數傳遞給其他函數的函數,目的是讓接收函數在某個時刻執行回呼。這對於您希望確保某些程式碼在特定任務完成後運行的場景非常有用,例如從伺服器取得資料或完成計算後。
回呼如何運作:
範例 1
function fetchData(callback) { // Simulate fetching data with a delay setTimeout(() => { const data = "Data fetched"; callback(data); // Call the callback function with the fetched data }, 1000); } function processData(data) { console.log("Processing:", data); } fetchData(processData); // fetchData will call processData with the data
範例 2
// Function that adds two numbers and uses a callback to return the result function addNumbers(a, b, callback) { const result = a + b; callback(result); // Call the callback function with the result } // Callback function to handle the result function displayResult(result) { console.log("The result is:", result); } // Call addNumbers with the displayResult callback addNumbers(5, 3, displayResult);
注意:我認為回調對於處理非同步操作是有效的,但要小心:隨著程式碼複雜性的增加,尤其是嵌套回調,您可能會遇到稱為回調地獄的問題。當回呼彼此深度嵌套時,就會出現此問題,從而導致可讀性問題並使程式碼難以維護。
回調地獄(也稱為末日金字塔)指的是有多個嵌套回呼的情況。當您需要按順序執行多個非同步操作,並且每個操作都依賴前一個操作時,就會發生這種情況。
例如。這會創造一個難以閱讀和維護的「金字塔」結構。
fetchData(function(data1) { processData1(data1, function(result1) { processData2(result1, function(result2) { processData3(result2, function(result3) { console.log("Final result:", result3); }); }); }); });
回調地獄問題:
使用回呼時,通常使用稱為錯誤優先回呼的模式。在此模式中,回調函數將錯誤作為其第一個參數。如果沒有錯誤,第一個參數通常為 null 或未定義,實際結果會作為第二個參數提供。
function fetchData(callback) { setTimeout(() => { const error = null; // Or `new Error("Some error occurred")` if there's an error const data = "Data fetched"; callback(error, data); // Pass error and data to the callback }, 1000); } function processData(error, data) { if (error) { console.error("Error:", error); return; } console.log("Processing:", data); } fetchData(processData); // `processData` will handle both error and data
注意:在回調之後,引入了 Promise 來處理 JavaScript 中的非同步過程。我們現在將更深入地研究 Promise 並探索它們在幕後是如何運作的。
Promises 是表示非同步操作最終完成(或失敗)及其結果值的物件。與回調相比,它們提供了一種更簡潔的方式來處理非同步程式碼。
承諾的目的:
A Promise can be in one of three states:
Note: If you want to explore more, you should check out Understand How Promises Work Under the Hood where I discuss how promises work under the hood.
example 1
// Creating a new promise const myPromise = new Promise((resolve, reject) => { const success = true; // Simulate success or failure if (success) { resolve("Operation successful!"); // If successful, call resolve } else { reject("Operation failed!"); // If failed, call reject } }); // Using the promise myPromise .then((message) => { console.log(message); // Handle the successful case }) .catch((error) => { console.error(error); // Handle the error case });
example 2
const examplePromise = new Promise((resolve, reject) => { setTimeout(() => { const success = Math.random() > 0.5; // Randomly succeed or fail if (success) { resolve("Success!"); } else { reject("Failure."); } }, 1000); }); console.log("Promise state: Pending..."); // To check the state, you would use `.then()` or `.catch()` examplePromise .then((message) => { console.log("Promise state: Fulfilled"); console.log(message); }) .catch((error) => { console.log("Promise state: Rejected"); console.error(error); });
Chaining allows you to perform multiple asynchronous operations in sequence, with each step depending on the result of the previous one.
Chaining promises is a powerful feature of JavaScript that allows you to perform a sequence of asynchronous operations where each step depends on the result of the previous one. This approach is much cleaner and more readable compared to deeply nested callbacks.
Promise chaining involves connecting multiple promises in a sequence. Each promise in the chain executes only after the previous promise is resolved, and the result of each promise can be passed to the next step in the chain.
function step1() { return new Promise((resolve) => { setTimeout(() => resolve("Step 1 completed"), 1000); }); } function step2(message) { return new Promise((resolve) => { setTimeout(() => resolve(message + " -> Step 2 completed"), 1000); }); } function step3(message) { return new Promise((resolve) => { setTimeout(() => resolve(message + " -> Step 3 completed"), 1000); }); } // Chaining the promises step1() .then(result => step2(result)) .then(result => step3(result)) .then(finalResult => console.log(finalResult)) .catch(error => console.error("Error:", error));
Disadvantages of Chaining:
While chaining promises improves readability compared to nested callbacks, it can still become unwieldy if the chain becomes too long or complex. This can lead to readability issues similar to those seen with callback hell.
Note: To address these challenges, async and await were introduced to provide an even more readable and straightforward way to handle asynchronous operations in JavaScript.
async and await are keywords introduced in JavaScript to make handling asynchronous code more readable and easier to work with.
async function fetchData() { return new Promise((resolve) => { setTimeout(() => { resolve("Data fetched"); }, 1000); }); } async function getData() { const data = await fetchData(); // Wait for fetchData to resolve console.log(data); // Logs "Data fetched" } getData();
1. Async Functions Always Return a Promise:
No matter what you return from an async function, it will always be wrapped in a promise. For example:
async function example() { return "Hello"; } example().then(console.log); // Logs "Hello"
Even though example() returns a string, it is automatically wrapped in a promise.
2. Await Pauses Execution:
The await keyword pauses the execution of an async function until the promise it is waiting for resolves.
async function example() { console.log("Start"); const result = await new Promise((resolve) => { setTimeout(() => { resolve("Done"); }, 1000); }); console.log(result); // Logs "Done" after 1 second } example();
In this example:
Handling errors with async/await is done using try/catch blocks, which makes error handling more intuitive compared to promise chains.
async function fetchData() { throw new Error("Something went wrong!"); } async function getData() { try { const data = await fetchData(); console.log(data); } catch (error) { console.error("Error:", error.message); // Logs "Error: Something went wrong!" } } getData();
With Promises, you handle errors using .catch():
fetchData() .then(data => console.log(data)) .catch(error => console.error("Error:", error.message));
Using async/await with try/catch often results in cleaner and more readable code.
You can use async/await with existing promise-based functions seamlessly.
example
function fetchData() { return new Promise((resolve) => { setTimeout(() => { resolve("Data fetched"); }, 1000); }); } async function getData() { const data = await fetchData(); // Wait for the promise to resolve console.log(data); // Logs "Data fetched" } getData();
Best Practices:
async and wait menyediakan cara yang lebih bersih dan mudah dibaca untuk mengendalikan operasi tak segerak berbanding dengan rantaian janji tradisional dan panggil balik. Dengan membenarkan anda menulis kod tak segerak yang kelihatan dan berkelakuan seperti kod segerak, ia memudahkan logik kompleks dan meningkatkan pengendalian ralat dengan blok cuba/tangkap. Menggunakan async/menunggu dengan janji menghasilkan kod yang lebih mudah diselenggara dan difahami.
Atas ialah kandungan terperinci Menguasai Corak Async JavaScript: Daripada Panggilan Balik kepada Async/Await. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!