Fetch là một HTTP client dựa trên promise
fetch có sẵn trong trình duyệt và Node hiện đại. Bạn đưa cho nó một URL, nó trả về một Promise resolve thành một đối tượng Response. Về cốt lõi, toàn bộ API chỉ có vậy:
Có hai lời gọi .then vì có hai bước bất đồng bộ: đầu tiên các header của response đến (đó là thứ promise đầu tiên resolve), sau đó phần thân được đọc và phân tích (response.json() cũng là một promise). Phần thân chưa được tải về cho đến khi bạn yêu cầu.
Cùng luồng đó với async/await đọc như code bình thường từ trên xuống dưới:
Hai await, hai điểm tạm dừng. Cùng công việc, thứ tự đọc rõ ràng hơn.
Đối tượng Response
Thứ bạn nhận về không phải phần thân, mà là một đối tượng Response chứa siêu dữ liệu và các phương thức để đọc phần thân theo nhiều dạng khác nhau:
Bạn có thể đọc phần thân bằng .json(), .text(), .blob(), .arrayBuffer() hoặc .formData(). Mỗi phương thức trả về một promise. Phần thân chỉ đọc được một lần: gọi .json() hai lần trên cùng một response thì lần thứ hai sẽ ném lỗi.
Cái bẫy lớn: lỗi HTTP không gây reject
Điều này khiến gần như mọi người mới dùng fetch đều vấp. Một response 404 hay 500 không phải là reject. Promise vẫn resolve bình thường, với response.ok === false. Fetch chỉ reject khi bản thân request không thể hoàn tất: lỗi DNS, không có mạng, bị CORS chặn.
Nghĩa là một lời gọi fetch ngây thơ sẽ vui vẻ đưa bạn một trang lỗi rồi gây lỗi ở .json() sau đó:
Cách sửa là tự kiểm tra response.ok và ném lỗi nếu máy chủ trả về mã trạng thái lỗi:
Hãy tập quen với việc viết khối if (!response.ok) đó. Nó nên có mặt trong mọi hàm bọc fetch bạn viết.
Gửi request POST
GET là mặc định. Với mọi phương thức khác, hãy truyền đối số thứ hai, một đối tượng tùy chọn:
Ba điều đáng lưu ý:
methodmặc định là"GET". Hãy đặt rõ ràng cho POST, PUT, DELETE, PATCH.bodynhận một chuỗi (hoặcFormData,Blobvà các kiểu tương tự): fetch không tự tuần tự hóa đối tượng cho bạn.JSON.stringify(...)là việc của bạn.- Header
Content-Typecho máy chủ biết cách phân tích phần thân. Quên nó thì hầu hết máy chủ sẽ coi phần thân là văn bản thuần.
Header, query string và các tùy chọn khác
Header chỉ là một đối tượng (hoặc một instance Headers). Query string thì bạn tự tạo, thường bằng URLSearchParams:
URLSearchParams lo phần mã hóa cho bạn (dấu cách, dấu &, unicode), để bạn không bị URL hỏng khi đầu vào có ký tự cần escape.
Các tùy chọn khác bạn sẽ gặp trong code thực tế: credentials: "include" để gửi cookie khác nguồn gốc, cache: "no-store" để bỏ qua bộ nhớ đệm HTTP, mode: "cors" (thường là mặc định) để điều khiển hành vi CORS.
Hủy request bằng AbortController
Đôi khi bạn muốn bỏ cuộc: người dùng đã gõ một từ khóa tìm kiếm mới, hoặc request chạy quá lâu. AbortController là cơ chế cho việc này:
controller.abort() khiến promise của fetch reject với một DOMException có name là "AbortError". Khối finally xóa bộ đếm giờ để một request thành công không để lại bộ đếm giờ treo lơ lửng.
Mẫu này (fetch, cộng thời gian chờ tối đa, cộng dọn dẹp) đáng được gói vào một hàm hỗ trợ và dùng lại ở mọi nơi.
Một hàm bọc dùng lại được
Ghép tất cả lại, bạn có một hàm hỗ trợ nhỏ xử lý phần code lặp lại một lần cho xong:
Một chỗ để đổi header, một chỗ để xử lý lỗi, một chỗ để xử lý response rỗng. Ứng dụng nào có quy mô kha khá rồi cũng sẽ có một thứ như thế này.
Tiếp theo: xử lý lỗi trong code bất đồng bộ
Fetch là một trong những nơi lỗi bất đồng bộ xuất hiện nhiều nhất, và kiểm tra response.ok chỉ là một mảnh ghép. Trang tiếp theo nói về xử lý lỗi với promise và async/await: lỗi đi đâu, cách bắt chúng, và những cái bẫy khiến lỗi lọt qua âm thầm.
Câu hỏi thường gặp
Làm sao dùng fetch trong JavaScript?
Gọi fetch(url) với URL bạn cần. Nó trả về một Promise resolve thành một đối tượng Response. Gọi response.json() (cũng là một promise) để phân tích phần thân. Với async/await: const res = await fetch(url); const data = await res.json();.
Làm sao gửi request POST bằng fetch?
Truyền đối số thứ hai gồm method: 'POST', một đối tượng headers (thường là 'Content-Type': 'application/json') và một body; hãy chuyển đối tượng thành chuỗi bằng JSON.stringify(...). Fetch không tự tuần tự hóa phần thân cho bạn.
Tại sao fetch không reject khi gặp 404 hoặc 500?
Fetch chỉ reject khi lỗi mạng: lỗi DNS, mất kết nối, bị CORS chặn. Với promise, các mã trạng thái lỗi HTTP vẫn là response thành công. Bạn phải tự kiểm tra response.ok (true với mã từ 200 đến 299) hoặc response.status và ném lỗi nếu máy chủ trả về lỗi.
Có thể hủy một request fetch không?
Có, bằng AbortController. Tạo một controller, truyền signal của nó vào fetch qua đối tượng tùy chọn, rồi gọi controller.abort() khi muốn hủy. Promise của fetch sẽ reject với một AbortError mà bạn có thể xử lý trong catch.