Bài trước: Web nâng cao (9) - JavaScript cho React (8) - Promise
-----1.1.1
Fetch API căn bản
Như đã đề cập ở các phần trước, để trao đổi thông tin giữa
trình duyệt và server, bạn đã được làm quen với đối tượng XMLHttpRequest của
JavaScript. Có một cách khác để trình duyệt có thể trao đổi thông tin với
server là sử dụng Fetch API.
Fetch có nghĩa là chạy đến và mang về một cái gì đó, do vậy,
có thể tạm hiểu trình duyệt sẽ dùng fetch để kết nối đến server và mang thông
tin về. API là viết tắt của Application Programming Interface, là công cụ
(phương thức, hàm) dùng để lập trình giao tiếp giữa các phần mềm, thiết bị với
nhau.
Fetch API là một thành phần của JavaScript, được sử dụng để
trao đổi thông tin giữa client-server. Bạn có thể sử dụng Fetch API để tạo HTTP
request, nhằm trao đổi dữ liệu giữa trình duyệt web và server.
Fetch API hoạt động dưới hình thức một promise; cho phép trao
đổi thông tin theo kiểu bất đồng bộ. Với Fetch API, việc lập trình giao tiếp giữa
trình duyệt và server được thực hiện đơn giản, rõ ràng và linh hoạt hơn so với
XMLHttpRequest. Hầu hết các trình duyệt đều đã hỗ trợ Fetch API.
Fetch là một phương thức toàn cục của JavaScript, do vậy bạn
có thể kiểm tra bằng đoạn mã sau:
console.log('fetch' in window);
// true
Fetch hoạt động dưới dạng một promise, nên nó sẽ tồn tại ở một
trong 3 trạng thái: trạng thái chờ (pending), trạng thái thực hiện thành công,
và trạng thái thực hiện thất bại.
Do fetch là phương thức có sẵn của JavaScript nên bạn không
cần quan tâm tới phần định nghĩa promise của fetch, mà chỉ quan tâm tới quá
trình gọi fetch để thực thi và xử lý kết quả do fetch trả về.
Để tạo một request, bạn sử dụng cú pháp sau:
fetch(url);
Trong đó, url chính là địa chỉ của server; trạng thái của
fetch khi mới được tạo ra sẽ ở trạng thái “chờ”. Ví dụ:
console.log(fetch("https://jsonplaceholder.typicode.com/todos/1"));
// pending
Nếu promise thực hiện thành công (resolved), khi đó hàm
callback trong then() sẽ được thực thi:
fetch(url)
.then( (response) => {
// đoạn mã xử lý cho trường hợp resolve được gọi
})
Nếu promise thực hiện thất bại (rejected), khi đó hàm
callback trong catch() sẽ được thực thi:
fetch(url)
.then( (response) => {
// đoạn mã xử lý cho trường hợp fetch thành công (resolve)
})
.catch( (error) => {
// đoạn mã xử lý cho trường hợp fetch thất bại
});
Ví dụ, sử dụng fetch để lấy thông tin người dùng trên mạng:
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then( (response)
=>
{
console.log("lay
duoc du lieu", response);
})
.catch(
(error)
=>
{
console.log("bi
loi", error);
});
Bạn có thể sửa URL
thành một đường dẫn không tồn tại (ví dụ : https://jsonplaceholder.typicode.com/todosabc/1), để fetch trả về lỗi? Nghĩa là phương thức
reject sẽ được thực thi? Tuy nhiên, kết quả không phải vậy. Phương thức resolve
vẫn được gọi và hàm callback trong catch không được thực thi. Tại sao có hiện
tượng này?
Nếu bạn mở đối tượng
response trong cửa sổ console của trình duyệt, bạn sẽ thấy trường status có giá
trị là 404, nghĩa là không tìm thấy tài nguyên. Vậy, phương thức reject được thực
thi khi nào? Thực tế nó chỉ được gọi khi mạng bị lỗi hoặc trình duyệt không thể
gọi API vì một lý do nào đó. Bạn thử ngắt kết nối mạng và chạy lại đoạn mã để có
được trạng thái reject của fetch.
Do fetch là một đối
tượng, nên ngoài các phương thức, nó còn có các thuộc tính đi kèm, như response.ok,
response.status; dựa vào giá trị của các thuộc tính này, bạn có thêm thông tin trước
khi thực hiện các xử lý tiếp theo. Ví dụ, thuộc tính response.ok sẽ có giá trị
là true nếu mã trạng thái của server
trả về (status) là 200-299, ngược lại là false; response.status sẽ trả về mã trạng
thái do server trả về. Bạn thử với các
URL khác nhau (đúng và sai) để xem kết quả trả về của response.status và
response.ok trong đoạn mã sau:
...
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then( (response) => {
console.log(response.status);
console.log(response.ok);
})
...
Bạn nên kiểm tra response.ok là true thì mới thực hiện các xử lý với dữ liệu từ server trả về:
...
fetch("https://jsonplaceholder.typicode.com/todosa/1")
.then( (response) => {
if(!response.ok) {
console.log("Khong
co du lieu", response.status);
}
// doan ma xu ly
du lieu
})
...
Dữ liệu server trả
về sẽ ở dạng luồng dữ liệu (stream), nên bạn có thể chuyển nó sang dạng JSON để
xử lý. Lưu ý: phương thức response.json() không trả về dữ liệu dạng JSON, mà nó
trả về một promise mới, phương thức resolve của promise này sẽ trả về dữ liệu dạng
JSON. Ví dụ:
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then( (response) => {
if(!response.ok) {
console.log("Khong
co du lieu", response.status);
}
return response.json();
})
.then ( (dataJSON) => {
console.log(dataJSON);
})
.catch( (error) => {
console.log("bi
loi", error);
});
1.1.2
Lập trình bất đồng bộ bằng async/await
Phiên bản ES6 đã hỗ trợ kĩ thuật lập trình bất đồng bộ bằng
promise. Với ES7, bạn có thêm một kĩ thuật lập trình bất đồng bộ nữa là
async/await. Ưu điểm của async/await so với promise là viết mã đơn giản, dễ đọc,
dễ kiểm tra lỗi.
Với promise, để viết một hàm nghiệp vụ chạy theo kiểu bất đồng
bộ, bạn phải nhúng promise vào trong hàm nghiệp vụ đó, ví dụ:
const layDuLieu = () => {
return new Promise( (resolve, reject) => {
});
};
Với async/await, cách làm cũng tương tự, dùng từ khóa async
(viết tắt của asynchronous – bất đồng bộ) để khai báo một hàm sẽ chạy theo kiểu
bất đồng bộ, ví dụ:
const layDuLieu = async () => {
};
Vậy là hàm layDuLieu() sẽ chạy theo chế độ bất đồng bộ, và
khi thực thi, hàm layDuLieu() sẽ trả về một promise.
const
layDuLieu =
async
()
=>
{
};
const
test
=
layDuLieu();
console.log(test);
// Promise {<fulfilled>: undefined}
Giả sử trong hàm layDuLieu(), chúng ta sử dụng fetch để lấy
dữ liệu từ server về client, chúng ta có thể viết như sau:
const
layDuLieu =
async
()
=>
{
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then( (response) => {
return response.json();
})
.then ( (dataJSON) => {
console.log(dataJSON);
})
};
layDuLieu();
// {userId: 1, id: 1, title: 'delectus aut autem', completed: false}
Ở đoạn mã trên, phương thức fetch() lại là một promise, do vậy
JavaScript sẽ tách fetch() ra khỏi luồng chính của chương trình, đợi cho tới
khi fetch() thực thi xong, nó sẽ gọi các hàm callback trong phương thức then().
Với từ khóa await (nghĩa là “chờ đợi”) đặt phía trước fetch() sẽ thay thế vai
trò của các phương thức then(). Đoạn mã trên sẽ được viết lại như sau:
const layDuLieu = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const dataJSON = await response.json();
return dataJSON;
};
layDuLieu().then( (data) => {
console.log(data);
});
// {userId: 1, id: 1, title: 'delectus aut autem', completed: false}
Các phương thức nằm sau từ khóa await sẽ trả về một promise.
Nên khi thực thi hàm layDuLieu(), bạn sẽ sử dụng các phương thức then() và
catch để xử lý cho các trường hợp promise thực hiện thành công hoặc thất bại.
Ví dụ cho trường hợp promise trả về thất bại, bạn thử chạy
đoạn mã sau đây sau khi đã ngắt kết nối mạng để xem kết quả.
const layDuLieu
=
async
()
=>
{
const
response
=
await
fetch("https://jsonplaceholder.typicode.com/todos/1");
const dataJSON = await response.json();
return dataJSON;
};
layDuLieu()
.then( (data) => {
console.log(data);
})
.catch( (error) => {
console.log("loi: ", error.message);
});
// loi: Failed to fetch
Bạn hoàn toàn có thể cho promise trả về thất bại (rejected)
khi có lỗi trong quá trình xử lý. Ví dụ, mặc định với status là 404, fetch() vẫn
trả về response, tuy nhiên, thực tế là không lấy được dữ liệu. Trong trường hợp
này, bạn sẽ sử dụng từ khóa throw để
chủ động trả về thất bại. Ví dụ:
const layDuLieu
=
async
()
=>
{
const
response
=
await
fetch("https://jsonplaceholder.typicode.com/todosâbc/1");
if (response.status !== 200) {
throw new Error('Khong fetch duoc du lieu!');
}
const dataJSON = await response.json();
return dataJSON;
};
layDuLieu()
.then( (data) => {
console.log(data);
})
.catch( (error) => {
console.log("loi:
",
error.message);
});
// loi:
Khong fetch duoc du lieu!
Một vài nhận xét:
– Kĩ thuật
async/await hoạt động trên nền tảng promise
– Từ khóa async đặt
trước một hàm sẽ làm cho hàm đó thực thi theo cơ chế bất đồng bộ
– Từ khóa await
chỉ được sử dụng trong các hàm có từ khóa async
– Với async/await,
cách viết đoạn chương trình chạy bất đồng bộ nhưng trông có vẻ giống như lập
trình đồng bộ
– Từ khóa await đứng
trước một promise (một hàm), nghĩa là chương trình sẽ chờ cho promise đó được xử
lý xong và trả về giá trị, trước khi thực thi các lệnh kế tiếp
1.1.3 Xem và đọc thêm
– Dùng các từ khóa
sau để tìm kiếm trên mạng và đọc thêm: lập trình đồng bộ, lập trình bất đồng bộ,
synchronous, asynchronous, promise, fetch API, async/await.
– Synchronous and Asynchronous: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing
– Asynchronous
videos: https://www.youtube.com/watch?v=ZcQyJ-gxke0&list=PL4cUxeGkcC9jx2TTZk3IGWKSbtugYdrlu&index=1
– XMLHttpRequest.readyState: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState
– HTTP response
status codes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
– async/await: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
– [1] Alex Banks, Eve Porcello, Learning React – Mordern Patterns
for Developing React Apps, O’Reilly Media, 2020, p23 – p
– [2] Darren Jones, JavaScript
Novice To Ninja, Sitepoint, 2017, p149 – p160
1.1.4
Bài tập và thực hành
Bài tập 1. Viết lại các đoạn mã trong phần lý thuyết để chạy
và kiểm tra lại kết quả.
Bài tập 2. Fetch và REST
https://www.youtube.com/watch?v=TRjVXmk8q8I
1.1.5
Câu hỏi ôn tập
Câu 1. Các kĩ thuật lập trình bất đồng bộ trong JavaScript gồm?
A. callback, promise, callback hell
B. promise, callback, fetch
C. async/await, callback, promise
D. fetch, async/await, callback
Câu 2. Trong JavaScript, callback được hiểu là?
A. Kĩ thuật khai báo biến
B. Xuất một chuỗi theo mẫu có sẵn
C. Truyền hàm cho hàm khác dưới dạng một đối số.
D. Kĩ thuật nối chuỗi
Câu 3. Trong JavaScript, khi sử dụng đối tượng
XMLHttpRequest để giao tiếp giữa client và server, các trạng thái của gói
request tính từ lúc được tạo ra tới khi thực hiện xong pha truyền dữ liệu, theo
thứ tự sẽ là:
A. UNSENT > OPENED > HEADER_RECEIVED > LOADING >
DONE
B. UNSENT > LOADING > HEADER_RECEIVED > OPENED >
DONE
C. LOADING > UNSENT > HEADER_RECEIVED > OPENED >
DONE
D. UNSENT > OPENED > LOADING > HEADER_RECEIVED >
DONE
Đáp án: 1 (C), 2 (C), 3 (A)
Cập nhật: 5/5/2022
-----
Bài tiếp: Web nâng cao (11) - JavaScript cho React (10)