Lấy dữ liệu bất đồng bộ với JavaScript Fetch API

Javascript nâng cao | by Học Javascript

Trong thời đại web hiện đại, việc giao tiếp giữa trình duyệt và máy chủ để lấy hoặc gửi dữ liệu là điều không thể thiếu. Những thao tác như tải danh sách sản phẩm, gửi thông tin người dùng hay cập nhật giao diện theo dữ liệu server đều cần đến khả năng xử lý bất đồng bộ của JavaScript. Trước đây, các lập trình viên thường sử dụng XMLHttpRequest, nhưng cú pháp rườm rà và khó bảo trì khiến nó dần được thay thế.

Fetch API ra đời như một giải pháp hiện đại, dễ sử dụng và mạnh mẽ hơn. Được tích hợp sẵn trong hầu hết các trình duyệt hiện nay, Fetch API cho phép chúng ta gửi và nhận dữ liệu một cách đơn giản, gọn gàng và hiệu quả, đặc biệt khi kết hợp với Promiseasync/await. Trong bài viết này, mình sẽ cùng tìm kiếm cách sử dụng Fetch API để lấy dữ liệu bất đồng bộ, xử lý lỗi, ứng dụng thực tế, cũng như một số ví dụ minh họa để hiểu rõ hơn về công cụ hữu ích này.

Tổng quan về Fetch API trong JavaScript

Fetch API là gì?

Fetch API là một Web API hiện đại do trình duyệt cung cấp, cho phép gửi các yêu cầu HTTP hoặc HTTPS từ phía client một cách bất đồng bộ mà không cần tải lại trang. Đây là sự thay thế gọn gàng và dễ dùng hơn cho XMLHttpRequest, vốn phức tạp và khó bảo trì.

Fetch hoạt động dựa trên cơ chế Promise, giúp viết mã rõ ràng và dễ xử lý hơn so với callback thuần. Điều này đặc biệt hữu ích khi cần xử lý chuỗi các hành động bất đồng bộ, như gọi API → lấy dữ liệu → hiển thị lên giao diện.

Cú pháp cơ bản của Fetch

fetch(url, options)
  .then(response => {
    if (!response.ok) {
      throw new Error("Lỗi mạng hoặc không tìm thấy dữ liệu");
    }
    return response.json(); // hoặc .text(), .blob(), .arrayBuffer()
  })
  .then(data => {
    console.log(data); // Xử lý dữ liệu thành công
  })
  .catch(error => {
    console.error("Đã xảy ra lỗi:", error); // Xử lý lỗi
  });

Giải thích:

  • url: đường dẫn đến API hoặc tài nguyên cần truy vấn.

  • options (tuỳ chọn): cấu hình phương thức (method), headers, body, v.v.

  • .then(): nhận kết quả trả về nếu thành công.

  • .catch(): xử lý lỗi nếu có vấn đề trong quá trình fetch.

Ví dụ đơn giản:

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then(res => res.json())
  .then(data => console.log("Bài viết:", data))
  .catch(err => console.error("Lỗi khi lấy dữ liệu:", err));

Kết quả là dữ liệu JSON của một bài viết được in ra console mà không cần reload trang, đồng thời xử lý lỗi cũng rất rõ ràng.

Các thành phần chính trong Fetch trong JavaScript

Fetch API sử dụng cú pháp linh hoạt, có thể cấu hình nhiều tuỳ chọn đi kèm để thực hiện các hành động như GET, POST, PUT, DELETE… Việc nắm rõ các thành phần chính sẽ giúp bạn sử dụng Fetch một cách hiệu quả hơn.

URL

  • địa chỉ của API hoặc tài nguyên mà bạn muốn gửi yêu cầu đến.

  • Ví dụ:

fetch("https://api.example.com/users");

Options (tuỳ chọn)

Tham số thứ hai của fetch() là một đối tượng cấu hình, nơi bạn có thể chỉ định:

method

  • Xác định loại yêu cầu HTTP bạn muốn gửi.

  • Giá trị mặc định là 'GET'.

Ví dụ:

method: 'POST'

headers

Dùng để khai báo loại dữ liệu bạn gửi/nhận (ví dụ: JSON, plain text...).

Thường dùng với các API RESTful để gửi và nhận JSON.

Ví dụ:

headers: {
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}

body

  • Là dữ liệu bạn muốn gửi đi – chỉ dùng với phương thức như POST, PUT.

  • Thường dùng JSON.stringify() để gửi object JavaScript.

Ví dụ:

body: JSON.stringify({
  name: "Nguyễn Văn A",
  age: 30
})

Ví dụ hoàn chỉnh:

fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ name: "Kiên", age: 25 })
})
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));

Đối tượng Response

Sau khi gọi fetch, kết quả trả về là một Promise chứa đối tượng Response.

Các thuộc tính và phương thức phổ biến:

  • response.ok: boolean, trả về true nếu status nằm trong khoảng 200–299.

  • response.status: mã trạng thái HTTP (ví dụ: 200, 404, 500…).

  • response.statusText: mô tả ngắn gọn đi kèm mã trạng thái (OK, Not Found...).

Các phương thức để lấy dữ liệu thực tế:

  • response.json(): giải mã JSON response.

  • response.text(): nhận nội dung dạng text.

  • response.blob(): dùng để xử lý ảnh, video, file nhị phân.

  • response.arrayBuffer(): dùng khi cần dữ liệu dạng buffer (âm thanh, video...).

Ví dụ:
fetch("https://api.example.com/data")
  .then(response => {
    if (response.ok) {
      return response.json();
    } else {
      throw new Error(`Lỗi: ${response.status} - ${response.statusText}`);
    }
  })
  .then(data => console.log("Dữ liệu:", data))
  .catch(error => console.error("Đã xảy ra lỗi:", error));

Kết hợp Fetch với async/await trong JavaScript

Kể từ ES2017, JavaScript hỗ trợ cú pháp async/await, giúp viết code bất đồng bộ trở nên dễ hiểu và gọn gàng hơn so với kiểu Promise chaining (.then().catch()). Khi kết hợp với Fetch API, async/await giúp quá trình gọi API, chờ phản hồi và xử lý lỗi trở nên trực quan và dễ kiểm soát hơn.

Ưu điểm khi dùng async/await với Fetch

  • Cú pháp rõ ràng, dễ đọc như mã đồng bộ.

  • Dễ dàng sử dụng với khối try...catch để xử lý lỗi.

  • Thích hợp cho các chuỗi xử lý phức tạp cần chờ kết quả theo thứ tự.

Cú pháp cơ bản

async function getData() {
  try {
    const response = await fetch('https://api.example.com/data');
    
    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Lỗi khi lấy dữ liệu:', error);
  }
}

Phân tích ví dụ

  • async function getData(): Định nghĩa một hàm bất đồng bộ.

  • await fetch(...): Gửi yêu cầu và đợi phản hồi từ server.

  • await response.json(): Đợi dữ liệu từ response chuyển sang dạng JSON.

  • try...catch: Bắt và xử lý các lỗi trong quá trình fetch hoặc parse JSON.

Thêm ví dụ nâng cao (gửi dữ liệu POST)

async function sendData() {
  try {
    const response = await fetch('https://api.example.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: 'Trung Kiên',
        age: 22
      })
    });

    if (!response.ok) {
      throw new Error(`Lỗi HTTP: ${response.status}`);
    }

    const result = await response.json();
    console.log('Kết quả:', result);
  } catch (error) {
    console.error('Gửi dữ liệu thất bại:', error);
  }
}

Xử lý lỗi trong Fetch trong JavaScript

Dù Fetch API rất mạnh mẽ và hiện đại, nhưng việc xử lý lỗi đúng cách là điều bắt buộc khi làm việc với API, để tránh gây ra trải nghiệm xấu cho người dùng hoặc lỗi ngầm trong hệ thống.

Fetch không "throw" lỗi HTTP

Một điểm quan trọng cần nhớ: fetch() chỉ throw lỗi khi có lỗi mạng (network error) hoặc khi request bị chặn, không tự động throw lỗi nếu server trả về status 4xx hay 5xx.

Ví dụ: nếu API trả về 404 Not Found, fetch() vẫn được coi là thành công, chỉ khi response.okfalse mới biết là lỗi logic từ phía server.

fetch('https://api.example.com/notfound')
  .then(response => {
    if (!response.ok) {
      throw new Error(`Lỗi HTTP: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Lỗi:', error));

Xử lý lỗi kết nối (Network Error)

Lỗi kết nối, mất mạng, domain không tồn tại... sẽ khiến fetch() throw ra lỗi, lúc này cần bắt bằng .catch() hoặc try...catch (với async/await):

async function getData() {
  try {
    const response = await fetch('https://domain-khong-ton-tai.com');

    if (!response.ok) {
      throw new Error(`Lỗi máy chủ: ${response.status}`);
    }

    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Lỗi kết nối hoặc dữ liệu:', error.message);
  }
}

Hiển thị lỗi thân thiện với người dùng

Không nên chỉ console.log lỗi – hãy cung cấp phản hồi rõ ràng cho người dùng, ví dụ:

catch (error) {
  document.getElementById('error-message').textContent =
    'Không thể tải dữ liệu. Vui lòng kiểm tra kết nối mạng hoặc thử lại sau.';
}

Gợi ý nâng cao:

Có thể phân loại lỗi:

  • Lỗi mạng → hiển thị "Không có kết nối mạng."

  • Lỗi server (500) → hiển thị "Lỗi hệ thống. Vui lòng thử lại sau."

  • Lỗi dữ liệu đầu vào (400) → hiển thị lỗi cụ thể.

Ứng dụng thực tế của Fetch API trong JavaScript

Fetch API là công cụ cực kỳ linh hoạt và hữu dụng trong lập trình web hiện đại, đặc biệt là với các ứng dụng yêu cầu giao tiếp với server mà không cần reload lại trang. Dưới đây là những tình huống phổ biến mà Fetch được sử dụng hiệu quả:

Gọi API để lấy danh sách sản phẩm, bài viết, dữ liệu động

Trong các website như blog, shop bán hàng, news… thường có nhu cầu lấy danh sách sản phẩm hoặc bài viết từ server. Fetch giúp gọi API và hiển thị dữ liệu mà không cần tải lại trang:

async function fetchPosts() {
  const response = await fetch('https://api.example.com/posts');
  const posts = await response.json();

  posts.forEach(post => {
    const item = document.createElement('div');
    item.textContent = post.title;
    document.body.appendChild(item);
  });
}

Ứng dụng:

  • Hiển thị sản phẩm theo trang (pagination).

  • Tìm kiếm bài viết, lọc dữ liệu động.

  • Tải thêm nội dung khi người dùng cuộn trang (infinite scroll).

Gửi form đăng ký, đăng nhập mà không reload trang

Thay vì submit form truyền thống (dẫn đến reload trang), có thể dùng Fetch để gửi thông tin qua API, sau đó xử lý phản hồi để hiển thị thông báo:

document.querySelector('form').addEventListener('submit', async (e) => {
  e.preventDefault();

  const formData = {
    username: e.target.username.value,
    password: e.target.password.value
  };

  const response = await fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(formData)
  });

  const result = await response.json();

  if (response.ok) {
    alert('Đăng nhập thành công!');
  } else {
    alert('Lỗi: ' + result.message);
  }
});

Ứng dụng:

  • Đăng ký tài khoản, đăng nhập.

  • Gửi bình luận, liên hệ, phản hồi.

  • Kiểm tra email trùng trong form đăng ký.

Tích hợp dữ liệu từ hệ thống bên ngoài (API bên thứ ba)

Fetch hỗ trợ gọi API từ các hệ thống ngoài như:

  • OpenWeather (thời tiết),

  • Google Maps,

  • News API,

  • RESTful API riêng của công ty khác.

Ví dụ: gọi API thời tiết từ OpenWeather:

fetch('https://api.openweathermap.org/data/2.5/weather?q=Hanoi&appid=YOUR_API_KEY')
  .then(res => res.json())
  .then(data => {
    console.log('Nhiệt độ:', data.main.temp);
  });
  • Xem thời tiết, tin tức, tỷ giá tiền tệ...

  • Kết nối hệ thống quản lý khác.

  • Lấy thông tin sản phẩm từ kho đối tác.

Tạo ứng dụng SPA, dashboard động

Fetch là "xương sống" của các ứng dụng Single Page Application (SPA), nơi toàn bộ giao diện được cập nhật động dựa trên dữ liệu từ API. Không cần reload từng trang:

// Khi chuyển tab
function loadTab(tab) {
  fetch(`/api/data?tab=${tab}`)
    .then(res => res.json())
    .then(data => {
      // Render nội dung tab
      document.getElementById('content').innerHTML = render(data);
    });
}

Ứng dụng:

  • Dashboard quản trị.

  • Website thống kê theo thời gian thực.

  • Ứng dụng quản lý khách hàng, đơn hàng, sản phẩm...

Một số ví dụ minh họa Fetch API trong JavaScript

Gửi yêu cầu GET để lấy dữ liệu JSON

Fetch thường được dùng để lấy dữ liệu từ server dưới dạng JSON — ví dụ như danh sách bài viết, sản phẩm hoặc người dùng:

fetch('https://api.example.com/users')
  .then(response => {
    if (!response.ok) {
      throw new Error('Không tìm thấy dữ liệu!');
    }
    return response.json();
  })
  .then(data => {
    console.log('Danh sách người dùng:', data);
  })
  .catch(error => {
    console.error('Lỗi khi gọi API:', error);
  });

Gửi POST chứa dữ liệu đăng nhập

Khi cần gửi thông tin như đăng nhập, đăng ký, bạn có thể dùng POST kèm dữ liệu dưới dạng JSON:

const user = {
  username: 'admin',
  password: '123456'
};

fetch('https://api.example.com/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(user)
})
.then(response => response.json())
.then(data => {
  console.log('Kết quả đăng nhập:', data);
})
.catch(error => {
  console.error('Lỗi:', error);
});

Xử lý lỗi nếu server trả về lỗi 404

Fetch không "tự động" ném lỗi cho các mã HTTP như 404 hoặc 500, nên ta cần kiểm tra response.ok để chủ động xử lý:

fetch('https://api.example.com/notfound')
  .then(response => {
    if (!response.ok) {
      throw new Error(`Lỗi ${response.status}: ${response.statusText}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Lỗi xảy ra:', error.message));

Tạo loading khi đang fetch dữ liệu

Để cải thiện UX, ta có thể hiển thị “Loading...” khi dữ liệu đang được tải:

<div id="loading">Đang tải dữ liệu...</div>
<div id="result"></div>
async function fetchData() {
  const loadingEl = document.getElementById('loading');
  const resultEl = document.getElementById('result');

  loadingEl.style.display = 'block';

  try {
    const res = await fetch('https://api.example.com/data');
    const data = await res.json();
    resultEl.innerText = JSON.stringify(data);
  } catch (err) {
    resultEl.innerText = 'Đã xảy ra lỗi!';
  } finally {
    loadingEl.style.display = 'none';
  }
}

fetchData();

Ưu điểm và hạn chế của Fetch API trong JavaScript

Ưu điểm

Cú pháp hiện đại, gọn gàng:

  • Không cần dùng XMLHttpRequest rườm rà.

  • Dễ kết hợp với async/await giúp mã rõ ràng, dễ bảo trì.

Dựa trên Promise:

  • Hỗ trợ chuỗi hành động .then(), .catch() rõ ràng.

  • Xử lý bất đồng bộ linh hoạt hơn so với callback truyền thống.

Tích hợp sẵn trong trình duyệt hiện đại:

  • Không cần cài đặt thư viện ngoài.

  • Được hỗ trợ tốt bởi hầu hết các trình duyệt phổ biến như Chrome, Firefox, Safari, Edge...

Hạn chế

Không tự động "bắt lỗi" HTTP:

  • Fetch chỉ "throw" khi có lỗi mạng (network error).

  • Các lỗi như 404, 500 không tự động bị ném ra, cần tự kiểm tra response.ok.

if (!response.ok) {
  throw new Error('Server trả về lỗi');
}

Không hỗ trợ timeout trực tiếp:

Fetch không có tuỳ chọn timeout, nên nếu server treo thì gọi mãi.

Để giải quyết, cần kết hợp với AbortController:

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);

fetch('https://api.example.com/data', { signal: controller.signal })
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error('Timeout hoặc lỗi:', err));

Kết bài

Fetch API là một công cụ mạnh mẽ và hiện đại giúp JavaScript giao tiếp hiệu quả với server thông qua các yêu cầu HTTP/HTTPS mà không làm gián đoạn giao diện người dùng. Với cú pháp đơn giản, dễ đọc, và khả năng kết hợp linh hoạt cùng Promise hoặc async/await, Fetch API ngày càng trở thành lựa chọn phổ biến trong phát triển web.

Tuy vẫn tồn tại một vài hạn chế nhỏ như không bắt lỗi HTTP tự động hay thiếu hỗ trợ timeout trực tiếp, nhưng với sự hỗ trợ rộng rãi từ các trình duyệt hiện đại và khả năng mở rộng bằng các kỹ thuật bổ trợ, Fetch API vẫn là công cụ không thể thiếu khi xây dựng các ứng dụng web tương tác, linh hoạt và mượt mà.

Sự am hiểu và thành thạo Fetch API sẽ giúp lập trình viên nâng cao khả năng xây dựng giao diện người dùng giàu trải nghiệm, cũng như dễ dàng tích hợp với các hệ thống bên ngoài thông qua RESTful API.

Bài viết liên quan