Tiếng Việt

Tìm hiểu cách sử dụng AbortController của JavaScript để hủy bỏ hiệu quả các thao tác bất đồng bộ như fetch request, timer, v.v., đảm bảo code sạch hơn và hiệu suất cao hơn.

JavaScript AbortController: Làm chủ việc Hủy bỏ các Thao tác Bất đồng bộ

Trong phát triển web hiện đại, các thao tác bất đồng bộ có mặt ở khắp mọi nơi. Việc tìm nạp dữ liệu từ API, đặt bộ đếm thời gian và xử lý tương tác của người dùng thường liên quan đến mã chạy độc lập và có khả năng kéo dài. Tuy nhiên, có những trường hợp bạn cần hủy bỏ các thao tác này trước khi chúng hoàn thành. Đây là lúc giao diện AbortController trong JavaScript ra tay cứu giúp. Nó cung cấp một cách sạch sẽ và hiệu quả để gửi tín hiệu yêu cầu hủy bỏ đến các thao tác DOM và các tác vụ bất đồng bộ khác.

Hiểu về Sự cần thiết của việc Hủy bỏ

Trước khi đi sâu vào các chi tiết kỹ thuật, hãy cùng tìm hiểu tại sao việc hủy bỏ các thao tác bất đồng bộ lại quan trọng. Hãy xem xét các tình huống phổ biến sau:

Giới thiệu AbortController và AbortSignal

Giao diện AbortController được thiết kế để giải quyết vấn đề hủy bỏ các thao tác bất đồng bộ. Nó bao gồm hai thành phần chính:

Cách sử dụng cơ bản: Hủy bỏ Fetch Request

Hãy bắt đầu với một ví dụ đơn giản về việc hủy bỏ một yêu cầu fetch:


const controller = new AbortController();
const signal = controller.signal;

fetch('https://api.example.com/data', { signal })
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('Data:', data);
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
    } else {
      console.error('Fetch error:', error);
    }
  });

// Để hủy yêu cầu fetch:
controller.abort();

Giải thích:

  1. Chúng ta tạo một đối tượng AbortController.
  2. Chúng ta lấy AbortSignal liên quan từ controller.
  3. Chúng ta truyền signal vào các tùy chọn của fetch.
  4. Nếu chúng ta cần hủy yêu cầu, chúng ta gọi controller.abort().
  5. Trong khối .catch(), chúng ta kiểm tra xem lỗi có phải là AbortError hay không. Nếu đúng, chúng ta biết rằng yêu cầu đã bị hủy bỏ.

Xử lý AbortError

Khi controller.abort() được gọi, yêu cầu fetch sẽ bị từ chối với một AbortError. Điều quan trọng là phải xử lý lỗi này một cách thích hợp trong mã của bạn. Nếu không làm vậy có thể dẫn đến các promise rejection không được xử lý và hành vi không mong muốn.

Đây là một ví dụ mạnh mẽ hơn với việc xử lý lỗi:


const controller = new AbortController();
const signal = controller.signal;

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data', { signal });
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    const data = await response.json();
    console.log('Data:', data);
    return data;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
      return null; // Hoặc ném lỗi để được xử lý ở cấp cao hơn
    } else {
      console.error('Fetch error:', error);
      throw error; // Ném lại lỗi để được xử lý ở cấp cao hơn
    }
  }
}

fetchData();

// Để hủy yêu cầu fetch:
controller.abort();

Các phương pháp tốt nhất để xử lý AbortError:

Hủy bỏ Timer với AbortSignal

AbortSignal cũng có thể được sử dụng để hủy bỏ các timer được tạo bằng setTimeout hoặc setInterval. Điều này đòi hỏi một chút công việc thủ công hơn, vì các hàm timer tích hợp không hỗ trợ trực tiếp AbortSignal. Bạn cần tạo một hàm tùy chỉnh để lắng nghe tín hiệu hủy và xóa timer khi nó được kích hoạt.


function cancellableTimeout(callback, delay, signal) {
  let timeoutId;

  const timeoutPromise = new Promise((resolve, reject) => {
    timeoutId = setTimeout(() => {
      resolve(callback());
    }, delay);

    signal.addEventListener('abort', () => {
      clearTimeout(timeoutId);
      reject(new Error('Timeout Aborted'));
    });
  });

  return timeoutPromise;
}

const controller = new AbortController();
const signal = controller.signal;


cancellableTimeout(() => {
  console.log('Timeout executed');
}, 2000, signal)
.then(() => console.log("Timeout finished successfully"))
.catch(err => console.log(err));

// Để hủy timeout:
controller.abort();

Giải thích:

  1. Hàm cancellableTimeout nhận một callback, một khoảng thời gian trễ, và một AbortSignal làm đối số.
  2. Nó thiết lập một setTimeout và lưu trữ ID của timeout.
  3. Nó thêm một event listener vào AbortSignal để lắng nghe sự kiện abort.
  4. Khi sự kiện abort được kích hoạt, event listener sẽ xóa timeout và từ chối promise.

Hủy bỏ Event Listener

Tương tự như timer, bạn có thể sử dụng AbortSignal để hủy bỏ các event listener. Điều này đặc biệt hữu ích khi bạn muốn xóa các event listener liên quan đến một component đang được gỡ bỏ.


const controller = new AbortController();
const signal = controller.signal;

const button = document.getElementById('myButton');

button.addEventListener('click', () => {
  console.log('Button clicked!');
}, { signal });

// Để hủy event listener:
controller.abort();

Giải thích:

  1. Chúng ta truyền signal như một tùy chọn cho phương thức addEventListener.
  2. Khi controller.abort() được gọi, event listener sẽ tự động được xóa bỏ.

AbortController trong Component React

Trong React, bạn có thể sử dụng AbortController để hủy bỏ các thao tác bất đồng bộ khi một component được gỡ bỏ. Điều này rất cần thiết để ngăn chặn rò rỉ bộ nhớ và lỗi gây ra bởi việc cập nhật các component đã được gỡ bỏ. Dưới đây là một ví dụ sử dụng hook useEffect:


import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    async function fetchData() {
      try {
        const response = await fetch('https://api.example.com/data', { signal });
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        const data = await response.json();
        setData(data);
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          console.error('Fetch error:', error);
        }
      }
    }

    fetchData();

    return () => {
      controller.abort(); // Hủy yêu cầu fetch khi component được gỡ bỏ
    };
  }, []); // Mảng phụ thuộc rỗng đảm bảo effect này chỉ chạy một lần khi mount

  return (
    
{data ? (

Data: {JSON.stringify(data)}

) : (

Loading...

)}
); } export default MyComponent;

Giải thích:

  1. Chúng ta tạo một AbortController bên trong hook useEffect.
  2. Chúng ta truyền signal vào yêu cầu fetch.
  3. Chúng ta trả về một hàm dọn dẹp từ hook useEffect. Hàm này sẽ được gọi khi component được gỡ bỏ.
  4. Bên trong hàm dọn dẹp, chúng ta gọi controller.abort() để hủy yêu cầu fetch.

Các Trường hợp Sử dụng Nâng cao

Nối chuỗi các AbortSignal

Đôi khi, bạn có thể muốn nối chuỗi nhiều AbortSignal lại với nhau. Ví dụ, bạn có thể có một component cha cần hủy các thao tác trong các component con của nó. Bạn có thể đạt được điều này bằng cách tạo một AbortController mới và truyền signal của nó cho cả component cha và con.

Sử dụng AbortController với các Thư viện Bên thứ ba

Nếu bạn đang sử dụng một thư viện bên thứ ba không hỗ trợ trực tiếp AbortSignal, bạn có thể cần phải điều chỉnh mã của mình để hoạt động với cơ chế hủy của thư viện đó. Điều này có thể bao gồm việc bao bọc các hàm bất đồng bộ của thư viện trong các hàm của riêng bạn để xử lý AbortSignal.

Lợi ích của việc Sử dụng AbortController

Khả năng Tương thích Trình duyệt

AbortController được hỗ trợ rộng rãi trong các trình duyệt hiện đại, bao gồm Chrome, Firefox, Safari và Edge. Bạn có thể kiểm tra bảng tương thích trên MDN Web Docs để biết thông tin mới nhất.

Polyfill

Đối với các trình duyệt cũ hơn không hỗ trợ AbortController một cách tự nhiên, bạn có thể sử dụng một polyfill. Polyfill là một đoạn mã cung cấp chức năng của một tính năng mới hơn trên các trình duyệt cũ. Có một số polyfill cho AbortController có sẵn trực tuyến.

Kết luận

Giao diện AbortController là một công cụ mạnh mẽ để quản lý các thao tác bất đồng bộ trong JavaScript. Bằng cách sử dụng AbortController, bạn có thể viết mã sạch hơn, hiệu suất cao hơn và mạnh mẽ hơn, xử lý việc hủy bỏ một cách mượt mà. Cho dù bạn đang tìm nạp dữ liệu từ API, đặt bộ đếm thời gian hay quản lý các event listener, AbortController có thể giúp bạn cải thiện chất lượng tổng thể của các ứng dụng web của mình.

Tài liệu đọc thêm