Các hàm không đồng bộ cho phép bạn viết mã dựa trên lời hứa như thể mã đồng bộ.
Các hàm không đồng bộ được bật theo mặc định trong Chrome, Edge, Firefox và Safari. Đây thực sự là một công cụ tuyệt vời. Các đối tượng này cho phép bạn viết mã dựa trên lời hứa như thể mã này đồng bộ, nhưng không chặn luồng chính. Chúng làm cho mã không đồng bộ của bạn trở nên "thông minh" hơn và dễ đọc hơn.
Các hàm không đồng bộ hoạt động như sau:
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await promise;
} catch (rejectedValue) {
// …
}
}
Nếu sử dụng từ khoá async
trước một định nghĩa hàm, thì bạn có thể sử dụng await
trong hàm đó. Khi bạn await
một lời hứa, hàm này sẽ bị tạm dừng theo cách không chặn cho đến khi lời hứa đó được xử lý. Nếu lời hứa thực hiện, bạn sẽ nhận lại giá trị. Nếu lời hứa bị từ chối, thì giá trị bị từ chối sẽ được gửi.
Hỗ trợ trình duyệt
Ví dụ: ghi nhật ký một lần tìm nạp
Giả sử bạn muốn tìm nạp URL và ghi nhật ký phản hồi dưới dạng văn bản. Dưới đây là giao diện của công cụ này bằng cách sử dụng lời hứa:
function logFetch(url) {
return fetch(url)
.then((response) => response.text())
.then((text) => {
console.log(text);
})
.catch((err) => {
console.error('fetch failed', err);
});
}
Và dưới đây là tương tự khi sử dụng các hàm không đồng bộ:
async function logFetch(url) {
try {
const response = await fetch(url);
console.log(await response.text());
} catch (err) {
console.log('fetch failed', err);
}
}
Vẫn là số lượng dòng, nhưng tất cả lệnh gọi lại đều biến mất. Nhờ vậy, bạn sẽ dễ đọc hơn, đặc biệt là với những người không quen thuộc với lời hứa.
Giá trị trả về không đồng bộ
Các hàm không đồng bộ luôn trả về một hứa hẹn, cho dù bạn có sử dụng await
hay không. Lời hứa đó sẽ phân giải với bất kỳ nội dung nào mà hàm không đồng bộ trả về hoặc từ chối bằng bất kỳ giá trị nào mà hàm không đồng bộ gửi. Vì vậy, với:
// wait ms milliseconds
function wait(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function hello() {
await wait(500);
return 'world';
}
...việc gọi hello()
sẽ trả về lời hứa thực hiện bằng "world"
.
async function foo() {
await wait(500);
throw Error('bar');
}
...gọi foo()
sẽ trả về lời hứa sẽ từ chối bằng Error('bar')
.
Ví dụ: tạo câu trả lời theo thời gian thực
Lợi ích của các hàm không đồng bộ tăng lên trong các ví dụ phức tạp hơn. Giả sử bạn muốn truyền trực tuyến phản hồi trong khi đăng xuất các phân đoạn và trả về kích thước cuối cùng.
Ở đây là phần cam kết:
function getResponseSize(url) {
return fetch(url).then((response) => {
const reader = response.body.getReader();
let total = 0;
return reader.read().then(function processResult(result) {
if (result.done) return total;
const value = result.value;
total += value.length;
console.log('Received chunk', value);
return reader.read().then(processResult);
});
});
}
Hãy khám phá tôi nhé, Jake, "người sử dụng những lời hứa hẹn" Archibald. Hãy xem cách tôi gọi processResult()
bên trong chính nó để thiết lập vòng lặp không đồng bộ? Cách viết khiến tôi cảm thấy rất thông minh. Nhưng giống như hầu hết các mã "thông minh", bạn phải nhìn vào mã này để biết độ tuổi của nó, giống như một trong những hình ảnh mắt thần kỳ từ những năm 90.
Hãy thử lại với các hàm không đồng bộ:
async function getResponseSize(url) {
const response = await fetch(url);
const reader = response.body.getReader();
let result = await reader.read();
let total = 0;
while (!result.done) {
const value = result.value;
total += value.length;
console.log('Received chunk', value);
// get the next result
result = await reader.read();
}
return total;
}
Mọi tính năng "thông minh" đã biến mất rồi. Vòng lặp không đồng bộ khiến tôi cảm thấy tự hào khi được thay thế bằng một vòng lặp đáng tin cậy, nhàm chán và đáng tin cậy. Tốt hơn nhiều. Trong tương lai, bạn sẽ nhận được trình lặp không đồng bộ. Điều này sẽ thay thế vòng lặp while
bằng vòng lặp for-of, giúp việc này trở nên gọn gàng hơn.
Cú pháp hàm không đồng bộ khác
Tôi đã cho bạn thấy async function() {}
, nhưng bạn có thể sử dụng từ khoá async
với cú pháp hàm khác:
Các hàm mũi tên
// map some URLs to json-promises
const jsonPromises = urls.map(async (url) => {
const response = await fetch(url);
return response.json();
});
Phương thức đối tượng
const storage = {
async getAvatar(name) {
const cache = await caches.open('avatars');
return cache.match(`/avatars/${name}.jpg`);
}
};
storage.getAvatar('jaffathecake').then(…);
Phương thức của lớp
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jaffathecake').then(…);
Cẩn thận! Tránh sắp xếp quá theo tuần tự
Mặc dù bạn đang viết mã có vẻ đồng bộ, nhưng hãy đảm bảo bạn không bỏ lỡ cơ hội thực hiện các thao tác song song.
async function series() {
await wait(500); // Wait 500ms…
await wait(500); // …then wait another 500ms.
return 'done!';
}
Thao tác trên mất 1000 mili giây để hoàn thành, trong khi:
async function parallel() {
const wait1 = wait(500); // Start a 500ms timer asynchronously…
const wait2 = wait(500); // …meaning this timer happens in parallel.
await Promise.all([wait1, wait2]); // Wait for both timers in parallel.
return 'done!';
}
Thao tác trên mất 500 mili giây để hoàn tất vì cả hai lần chờ đều xảy ra cùng một lúc. Hãy xem một ví dụ thực tế.
Ví dụ: xuất các lần tìm nạp theo thứ tự
Giả sử bạn muốn tìm nạp một loạt URL và ghi nhật ký các URL đó càng sớm càng tốt, theo đúng thứ tự.
Hít thở sâu – đây là cách trông giống với lời hứa:
function markHandled(promise) {
promise.catch(() => {});
return promise;
}
function logInOrder(urls) {
// fetch all the URLs
const textPromises = urls.map((url) => {
return markHandled(fetch(url).then((response) => response.text()));
});
// log them in order
return textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise).then((text) => console.log(text));
}, Promise.resolve());
}
Vâng, đúng vậy, tôi đang sử dụng reduce
để tạo chuỗi các lời hứa. Tôi rất thông minh. Tuy nhiên, đây là một cách lập trình thông minh để bạn có thể thực hiện tốt hơn mà không cần.
Tuy nhiên, khi chuyển đổi hàm trên thành hàm không đồng bộ, bạn muốn quá tuần tự:
async function logInOrder(urls) { for (const url of urls) { const response = await fetch(url); console.log(await response.text()); } }
function markHandled(...promises) { Promise.allSettled(promises); } async function logInOrder(urls) { // fetch all the URLs in parallel const textPromises = urls.map(async (url) => { const response = await fetch(url); return response.text(); }); markHandled(...textPromises); // log them in sequence for (const textPromise of textPromises) { console.log(await textPromise); } }
Giải pháp hỗ trợ trình duyệt: trình tạo
Nếu đang nhắm đến các trình duyệt hỗ trợ trình tạo (bao gồm phiên bản mới nhất của mọi trình duyệt chính), thì bạn có thể sắp xếp các hàm không đồng bộ polyfill.
Babel sẽ thực hiện việc này cho bạn, sau đây là một ví dụ thông qua JDK REPL
- hãy lưu ý đến mức độ tương tự nhau của mã được chuyển mã. Quy tắc biến đổi này là một phần của giá trị đặt trước es2017 của Babel.
Bạn nên sử dụng phương pháp chuyển đổi mã nguồn, vì bạn có thể tắt tính năng này sau khi các trình duyệt mục tiêu của bạn hỗ trợ các hàm không đồng bộ, nhưng nếu thực sự không muốn dùng trình chuyển đổi mã nguồn, bạn có thể dùng polyfill của Babel để tự sử dụng. Thay vì:
async function slowEcho(val) {
await wait(1000);
return val;
}
...bạn sẽ thêm polyfill và ghi:
const slowEcho = createAsyncFunction(function* (val) {
yield wait(1000);
return val;
});
Xin lưu ý rằng bạn phải truyền trình tạo (function*
) đến createAsyncFunction
và sử dụng yield
thay vì await
. Ngoài ra, cách này vẫn hoạt động như cũ.
Giải pháp: trình tạo lại
Nếu bạn đang nhắm mục tiêu đến các trình duyệt cũ, JDK cũng có thể chuyển đổi trình tạo mã, cho phép bạn sử dụng các hàm không đồng bộ cho đến tận IE8. Để làm điều này, bạn cần có giá trị đặt trước es2017 của Babel và giá trị đặt trước es2015.
Kết quả không đẹp, vì vậy, hãy để ý đến tình trạng quá tải mã.
Không đồng bộ tất cả mọi thứ!
Sau khi các hàm không đồng bộ có mặt trên tất cả trình duyệt, hãy sử dụng chúng trên mọi hàm trả về đầy hứa hẹn! Các phần tử này không chỉ giúp mã của bạn gọn gàng hơn mà còn đảm bảo hàm đó sẽ luôn trả về một lời hứa.
Tôi thực sự hào hứng về các hàm không đồng bộ từ năm 2014 và thật tuyệt khi thấy các hàm này xuất hiện thực sự trong trình duyệt. Ôi!