首頁 > web前端 > js教程 > 掌握 JavaScript 非同步模式:從回呼到非同步/等待

掌握 JavaScript 非同步模式:從回呼到非同步/等待

Patricia Arquette
發布: 2024-09-13 20:16:39
原創
494 人瀏覽過

Mastering JavaScript Async Patterns: From Callbacks to Async/Await

當我第一次遇到非同步 JavaScript 時,我在回調方面遇到了困難,並且不知道 Promises 在幕後是如何運作的。隨著時間的推移,對 Promise 和 async/await 的了解改變了我的編碼方法,使其更易於管理。在本部落格中,我們將逐步探索這些非同步模式,揭示它們如何簡化您的開發流程並使您的程式碼更乾淨、更有效率。讓我們一起深入探討並揭開這些概念!

為什麼需要學習異步 JavaScript?

學習非同步 JavaScript 對於現代 Web 開發至關重要。它允許您有效地處理 API 請求等任務,使您的應用程式保持快速回應。掌握非同步技術(例如 Promises 和 async/await)不僅對於建立可擴展的應用程式至關重要,而且對於在 JavaScript 工作面試中取得成功也至關重要,而理解這些概念通常是重點。透過掌握非同步 JavaScript,您將提高編碼技能並更好地為現實世界的挑戰做好準備。

什麼是異步模式?

JavaScript 中的非同步模式是用於處理需要時間的任務的技術,例如從伺服器獲取數據,而不凍結應用程式。最初,開發人員使用回調來管理這些任務,但這種方法通常會導致程式碼複雜且難以閱讀,稱為「回調地獄」。為了簡化這一點,引入了 Promise,透過連結操作和更優雅地處理錯誤,提供了一種更乾淨的方式來處理非同步操作。 async/await 繼續發展,它允許您編寫看起來和行為更像同步程式碼的非同步程式碼,從而更易於閱讀和維護。這些模式對於建立高效、反應迅速的應用程式至關重要,也是現代 JavaScript 開發的基礎。我們將在本部落格中更詳細地探討這些概念。

什麼是回調?

回呼 是作為參數傳遞給其他函數的函數,目的是讓接收函數在某個時刻執行回呼。這對於您希望確保某些程式碼在特定任務完成後運行的場景非常有用,例如從伺服器取得資料或完成計算後。

回呼如何運作:

  1. 您定義一個函數(回呼)。
  2. 您將此函數作為參數傳遞給另一個函數。
  3. 接收函數在適當的時間執行回呼。

範例 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);
      });
    });
  });
});

登入後複製

回調地獄問題:

  1. 可讀性:程式碼變得難以閱讀和理解。
  2. 可維護性:進行更改或除錯變得具有挑戰性。
  3. 錯誤處理:管理錯誤可能會變得複雜。

使用回調處理錯誤

使用回呼時,通常使用稱為錯誤優先回呼的模式。在此模式中,回調函數將錯誤作為其第一個參數。如果沒有錯誤,第一個參數通常為 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 並探索它們在幕後是如何運作的。

Promise 簡介

Promises 是表示非同步操作最終完成(或失敗)及其結果值的物件。與回調相比,它們提供了一種更簡潔的方式來處理非同步程式碼。

承諾的目的:

  1. Avoid Callback Hell: Promises help manage multiple asynchronous operations without deep nesting.
  2. Improve Readability: Promises provide a more readable way to handle sequences of asynchronous tasks.

Promise States

A Promise can be in one of three states:

  1. Pending: The initial state, before the promise has been resolved or rejected.
  2. Fulfilled: The state when the operation completes successfully, and resolve has been called.
  3. Rejected: The state when the operation fails, and reject has been called.

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 Promises

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.

How Promise Chaining Works

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.

Introduction to Async/Await

async and await are keywords introduced in JavaScript to make handling asynchronous code more readable and easier to work with.

  • async: Marks a function as asynchronous. An async function always returns a promise, and it allows the use of await within it.
  • await: Pauses the execution of the async function until the promise resolves, making it easier to work with asynchronous results in a synchronous-like fashion.
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();

登入後複製

How Async/Await Works

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:

  • "Start" is logged immediately.
  • The await pauses execution until the promise resolves after 1 second.
  • "Done" is logged after the promise resolves.

Error Handling with Async/Await

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.

Combining Async/Await with Promises

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:

  1. 使用 async/await 來提高可讀性:在處理多個非同步操作時,async/await 可以讓程式碼更線性,更容易理解。
  2. 與 Promise 結合:繼續使用 async/await 和基於 Promise 的函數來更自然地處理複雜的非同步流程。
  3. 錯誤處理: 始終在非同步函數中使用 try/catch 區塊來處理潛在的錯誤。

結論

與傳統的 Promise 鍊和回調相比,async 和 wait 提供了一種更清晰、更易讀的方式來處理非同步操作。透過允許您編寫外觀和行為類似於同步程式碼的非同步程式碼,它們可以簡化複雜的邏輯並使用 try/catch 區塊改進錯誤處理。將 async/await 與 Promise 一起使用會產生更易於維護和理解的程式碼。

以上是掌握 JavaScript 非同步模式:從回呼到非同步/等待的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板