HomeLập trìnhJavaScriptLàm cách nào...

Làm cách nào để xử lý các cuộc gọi lại không đồng bộ trong JavaScript…Không có cuộc gọi lại?


Đang bàn tán về Discord ngày hôm nay, câu hỏi tương tự đã xuất hiện một vài lần trên một số máy chủ khác nhau. Tôi nghĩ đó là một câu hỏi hay và có vẻ như bộ não của tôi không hoạt động theo cách mà người khác mong đợi.

Đây là câu hỏi:

“Vì vậy, tôi có một fetch chức năng, và tôi đang làm một số then cùng với nó để phân tích dữ liệu JSON. Tôi muốn trả lại nó, nhưng làm thế nào tôi có thể? chúng ta không thể return một cái gì đó từ một cuộc gọi chức năng không đồng bộ!”

Đó là một câu hỏi tuyệt vời. Có rất nhiều thứ đang diễn ra ở đó. Chúng tôi có cách xử lý việc này trong React, khá dễ dàng: chúng tôi có thể useState để tạo một số biến trạng thái, chúng ta có thể chạy fetch trong vòng một useEffect và tải biến trạng thái đó, và chúng ta có thể sử dụng nữa useEffect để lắng nghe biến trạng thái đó thay đổi. Khi thay đổi xảy ra, chúng tôi có thể kích hoạt chức năng tùy chỉnh của mình và thực hiện một số tác dụng phụ với nó.

Với JavaScript, HTML và CSS thuần túy, nó sẽ trở nên phức tạp hơn một chút. Đối với những người thích đọc trang cuối cùng của cuốn tiểu thuyết bí ẩn trước phần còn lại, thì phần thay thế này là nơi chúng ta sẽ kết thúc.

Một khởi đầu xấu xí

Giả sử chúng tôi muốn tìm nạp một số việc cần làm từ máy chủ và khi chúng tôi đã tải chúng, chúng tôi muốn cập nhật DOM. Chúng tôi có thể cần tải lại chúng hoặc nối thêm chúng sau – chúng tôi muốn mọi thứ xảy ra nếu các chức năng không đồng bộ của chúng tôi thực hiện một số loại cập nhật cho tiểu bang.

Tuy nhiên, tôi thực sự không biết mình cảm thấy thế nào về điều đó. Khi chúng ta có một khối mã như thế này:

const load = () => {
  fetch("https://jsonplaceholder.typicode.com/todos")
    .then(res => res.json())
    .then(jsonObj => {
      const todoContainer = document.querySelector(".todos-container");
      // now, take each todo, create its DOM, and poke it in.
      jsonObj.forEach( (todo)=>{
        const todoEl = document.createElement("div");
        todoEl.classList.add("todo");
        const todoTitle = document.createElement("h3");
        todoTitle.classList.add("todo-title");
        todoTitle.textContent=todo.title;

        const todoStatus = document.createElement("div");
        todoStatus.classList.add("todo-status");
        todoStatus.textContent = todo.done ? "Complete" : "Incomplete";

        todoEl.append(todoTitle, todoStatus);
        todoContainer.append(todoEl)
    })
}

chúng tôi loại để điền vào DOM ngay tại đó trong .then() chặn, bởi vì chúng tôi thực sự không thể nói “này, khi hoàn tất, hãy tắt chức năng này.”

Chúng ta có thể chỉ cần đợi từng Lời hứa, thay vì xâu chuỗi chúng như thế này và chỉ cần trả về kết quả của phân tích cú pháp cuối cùng:

const load = async () => {
  const result = await fetch("https://jsonplaceholder.typicode.com/todos")
  const jsonObj = await result.json();
  const todoContainer = document.querySelector(".todos-container");

  jsonObj.forEach( (todo)=>{
    const todoEl = document.createElement("div");
    todoEl.classList.add("todo");
    const todoTitle = document.createElement("h3");
    todoTitle.classList.add("todo-title");
    todoTitle.textContent=todo.title;

    const todoStatus = document.createElement("div");
    todoStatus.classList.add("todo-status");
    todoStatus.textContent = todo.done ? "Complete" : "Incomplete";

    todoEl.append(todoTitle, todoStatus);
    todoContainer.append(todoEl)
  })
  // here, if we wanted, we could even return that object:
  return jsonObj;
}

// later, we can do this:
const todos = await load();
// fills the DOM and assigns all the todos to that variable

Bây giờ tốt hơn, của chúng tôi load() có thể được sử dụng để không chỉ đưa các phần tử đó vào DOM mà còn trả về dữ liệu cho chúng ta.

Đọc thêm  Có thể sử dụng toString để chuyển đổi entero và cadena de texto

Tuy nhiên, điều này vẫn chưa lý tưởng – chúng tôi vẫn phải điền vào DOM đó khi kết quả đang tải và chúng tôi vẫn phải đợi quá trình tải diễn ra. chúng tôi không có ý tưởng khi nào todos sẽ là một cái gì đó. Cuối cùng, nó sẽ xảy ra, nhưng chúng ta không biết khi nào.

Gọi lại, có ai không?

Chúng tôi có tùy chọn chức năng gọi lại. Nó có thể hữu ích, thay vì thực sự mã hóa cứng công cụ xây dựng DOM, để chuyển nó sang thứ khác. Nó làm cho load chức năng trừu tượng hơn, vì nó không được kết nối với một điểm cuối cụ thể.

Hãy xem nó trông như thế nào:

const load = async (apiEndpoint, callbackFn) => {
  const result = await fetch(apiEndpoint);
  if(!result.ok){
    throw new Error(`An error occurred: ${result.status}`)
  }
  // at this point, we have a good result:
  const jsonObj = await result.json();
  // run our callback function, passing in that object
  callbackFn(jsonObj)
}

// Let's use that. First, we'll make a callback function:
const todoHandler = (todos) => {
  const todoContainer = document.querySelector(".todos-container");

  todos.forEach( (todo)=>{
    const todoEl = document.createElement("div");
    todoEl.classList.add("todo");
    const todoTitle = document.createElement("h3");
    todoTitle.classList.add("todo-title");
    todoTitle.textContent=todo.title;

    const todoStatus = document.createElement("div");
    todoStatus.classList.add("todo-status");
    todoStatus.textContent = todo.done ? "Complete" : "Incomplete";

    todoEl.append(todoTitle, todoStatus);
    todoContainer.append(todoEl)
  })    
}

load("https://jsonplaceholder.typicode.com/todos", todoHandler);

Điều đó đẹp hơn – chúng tôi đang nói load tải cái gì và phải làm gì khi quá trình tìm nạp đó hoàn tất. Nó hoạt động. Và không có gì thực sự Sai lầm với. Tuy nhiên, nó có một số nhược điểm.

Cuộc gọi lại của tôi không có nghĩa là hoàn thành. Chúng tôi không xử lý lỗi, chúng tôi không thực sự đạt được bất cứ điều gì bằng cách tiếp cận này. Chúng tôi không lấy dữ liệu ra khỏi load chức năng theo bất kỳ nghĩa nào chúng ta có thể sử dụng, một cách kịp thời.

Và một lần nữa, tôi là tôi, tôi muốn thử một cách khác.

Cuộc gọi lại không có cuộc gọi lại

Được rồi, cái đó một chút sai lệch. Họ không gọi lại. Chúng tôi sẽ hoàn toàn tránh đang có gọi lại ở tất cả. Chúng ta sẽ có gì thay thế? Người nghe sự kiện!

DOM là tất cả về giao tiếp. Sự kiện diễn ra khắp nơi – sự kiện chuột, sự kiện bàn phím, cử chỉ, phương tiện và cửa sổ… Trình duyệt là một nơi ồn ào.

Nhưng nó là tất cả kiểm soátđó là tất cả có chủ đích và nó là tất cả đúng ngữ pháp. Mọi thứ được gói gọn một cách độc đáo, hoàn toàn độc lập, nhưng chúng có thể giao tiếp các sự kiện lên và xuống cây DOM khi cần thiết. Và chúng ta có thể tận dụng điều đó, với CustomEvent API.

Tạo ra một CustomEvent thực sự không khó lắm, chỉ cần cung cấp tên của sự kiện dưới dạng một chuỗi và khối hàng – thông tin được bao gồm trong sự kiện đó. Đây là một ví dụ:

const myShoutEvent = new CustomEvent('shout', {
  detail: {
    message: 'HELLO WORLD!!',
    timeSent: new Date() 
  }
})

// and later on, we can send that event:
someDomEl.dispatchEvent(myShoutEvent);

Đó là tất cả những gì cần có cho một sự kiện tùy chỉnh. Chúng tôi tạo sự kiện, bao gồm tùy chỉnh detail dữ liệu, và sau đó chúng tôi dispatchEvent trên một nút DOM nhất định. Khi sự kiện đó được kích hoạt trên nút DOM đó, nó sẽ tham gia vào luồng giao tiếp bình thường, tiếp tục theo các giai đoạn sủi bọt và ghi lại giống như bất kỳ sự kiện bình thường nào – bởi vì nó một sự kiện bình thường.

Đọc thêm  ... trong JavaScript – Toán tử ba dấu chấm trong JS

Điều này giúp chúng ta như thế nào?

Điều gì sẽ xảy ra nếu chúng ta nghe cho sự kiện tùy chỉnh đó ở đâu đó và đặt trách nhiệm xử lý sự kiện đó (và detail) với người nhận, thay vì nói với người nhận load chức năng phải làm gì khi chúng tôi nhận được dữ liệu đó?

Với cách tiếp cận này, chúng tôi không thực sự quan tâm khi nào quá trình tìm nạp hoàn thành quá trình xử lý của nó, chúng tôi không quan tâm đến một số giá trị trả về trong một số biến toàn cục – chúng tôi chỉ yêu cầu nút DOM gửi một sự kiện… và chuyển dữ liệu đã tìm nạp thành detail.

Hãy bắt đầu chơi với ý tưởng này:

const load = (apiEndpoint, elementToNotify, eventTitle) => {
  fetch(apiEndpoint)
    .then( result => result.json() )
    .then( data => {
       // here's where we do this: we want to create that custom event
       const customEvent = new CustomEvent(eventTitle, {
         detail: {
           data
         }
       });
       // now, we simply tell the element to do its thing:
      elementToNotify.dispatchEvent(customEvent)
     })
};

Đó là nó. Đó là toàn bộ shebang. Chúng tôi tải một số điểm cuối, chúng tôi phân tích cú pháp điểm cuối đó, chúng tôi đưa dữ liệu vào một đối tượng sự kiện tùy chỉnh và đưa nó vào DOM.

Phần còn lại nằm ngoài mối quan tâm của điều đó load chức năng. nó không quan tâm về dữ liệu trông như thế nào, nó không quan tâm nó đến từ đâu, nó không trở lại bất cứ điều gì. Nó thực hiện một việc – tìm nạp dữ liệu và sau đó hét lên về nó.

Bây giờ, với điều đó tại chỗ, làm thế nào chúng ta có thể kết nối nó từ phía bên kia?

// a function to create the Todo element in the DOM...
const createTodo = ({id, title, completed}) => {
  const todoEl = document.createElement("div");
  todoEl.classList.add("todo");

  const todoTitle = document.createElement("h3");
  todoTitle.classList.add("todo-title");
  todoTitle.textContent=todo.title;

  const todoStatus = document.createElement("div");
  todoStatus.classList.add("todo-status");
  todoStatus.textContent = todo.done ? "Complete" : "Incomplete";

  todoEl.append(todoTitle, todoStatus);
    
  return todoEl;
}

// and when that load event gets fired, we want this to be
//  the event listener.
const handleLoad = (event)=>{
  // pull the data out of the custom event...
  const data = event.detail.data;
  // and create a new todo for each object
  data.forEach( todo => {
    event.target.append( createTodo(todo) )
  })
}

// finally, we wire in our custom event!
container.addEventListener("todo.load", handleLoad)

Đó dây lên container để lắng nghe cho phong tục đó todo.load biến cố. Khi sự kiện xảy ra, nó sẽ kích hoạt và thực hiện điều đó handleLoad thính giả.

Nó không làm bất cứ điều gì đặc biệt kỳ diệu: nó chỉ đơn giản là lấy data từ đó event.detail chúng tôi tạo ra trong load chức năng. Sau đó handleLoad gọi createTodo cho từng đối tượng trong datatạo nút DOM của chúng tôi cho từng thành phần việc cần làm.

Đọc thêm  JavaScript viết hoa chữ cái đầu tiên – Cách viết hoa chữ cái đầu tiên trong một từ bằng JS

Bằng cách sử dụng phương pháp này, chúng tôi đã tách riêng các bit tìm nạp dữ liệu khỏi các bit trình bày. Điều duy nhất còn lại là bảo người này nói với người kia:

// remember, the parameters we defined were:
// apiEndpoint: url,
// elementToNotify: HTMLDomNode,
// eventTitle: string
load("https://jsonplaceholder.typicode.com/todos", container, 'todo.load');

Tóm lại

Chúng tôi bắt đầu với một mớ hỗn độn mã spaghetti xấu xí – tìm nạp logic trộn lẫn với phân tích cú pháp và trình bày. Không tốt. Ý tôi là, tất cả chúng ta đều làm điều đó, chúng ta sử dụng nó mọi lúc, nhưng nó chỉ cảm thấy sơ sài. Không có sự phân tách rõ ràng và không có cách nào làm việc với dữ liệu bên ngoài đó .then().

sử dụng async/awaitchúng tôi có thể trả lại dữ liệu đó và chúng tôi có thể sử dụng dữ liệu đó bên ngoài quá trình tìm nạp nếu cần – nhưng chúng tôi không có cách nào thực sự để biết khi nào dữ liệu đó đã được tải. Chúng tôi vẫn có thể xử lý nội tuyến, tải lớp trình bày bằng tìm nạp, nhưng điều đó không đạt được gì so với lần trước.

Sử dụng các cuộc gọi lại, chúng ta có thể bắt đầu tách biệt – với một cuộc gọi lại, chúng ta có thể tải dữ liệu và khi hoạt động không đồng bộ hoàn tất, hãy chạy chức năng gọi lại. Nó giữ cho chúng được phân tách độc đáo và nó chuyển dữ liệu vào hàm gọi lại dưới dạng tham số. Nó tốt hơn là trộn nội tuyến bản trình bày, nhưng chúng tôi có thể làm việc gì đó khác biệt.

Và tôi có nghĩa là khác biệt – sử dụng CustomEvent API không tốt hơn hoặc tệ hơn việc sử dụng các cuộc gọi lại. Cả hai đều có điểm mạnh và điểm yếu. Tôi thích sự sạch sẽ của CustomEvent hệ thống, tôi thích rằng chúng ta có thể mở rộng nó ra. Vài ví dụ:

  • một lớp Timer, kích hoạt một "timer.tick""timer.complete" biến cố. Nút gốc/vùng chứa của nút DOM của Bộ hẹn giờ đó có thể lắng nghe các sự kiện đó, kích hoạt không đồng bộvà phản hồi một cách thích hợp, cho dù đang cập nhật thời gian hiển thị hay gây ra phản ứng khi bộ hẹn giờ hoàn tất.
  • Todos của chúng tôi – chúng tôi có thể yêu cầu vùng chứa lắng nghe "todo.load", "todo.update", bất kỳ sự kiện tùy chỉnh nào chúng tôi thích. Chúng tôi có thể xử lý các bản cập nhật bằng cách tìm nút DOM có liên quan và cập nhật nội dung của nó hoặc xóa tất cả và thay thế chúng khi tải.

Chúng tôi đang tách logic mô hình khỏi logic trình bày toàn bộvà xác định một giao diện giữa hai. Sạch sẽ, rõ ràng, đáng tin cậy và đơn giản.



Zik.vn – Biên dịch & Biên soạn Lại

spot_img

Create a website from scratch

Just drag and drop elements in a page to get started with Newspaper Theme.

Buy Now ⟶

Bài viết liên quang

DMCA.com Protection Status