Cách dễ nhất để hiểu mã không đồng bộ là nhận ra rằng mã không thực thi tuần tự. Điều này có thể khó hiểu trong JavaScript, đặc biệt nếu bạn đến từ ngôn ngữ lập trình mặc định là đồng bộ hoặc tuần tự, như PHP
Trong bài đăng này, bạn sẽ học cách viết mã async [còn được gọi là 'không tuần tự'] trong JavaScript một cách hiệu quả. Bạn sẽ tìm hiểu kiến thức cơ bản về cách sử dụng lệnh gọi lại, lời hứa và kiểu async/await hiện đại
Bắt đầu nào
Lập trình không đồng bộ. Giới thiệu nhanh
Hãy bắt đầu với những điều cơ bản. Có hai mô hình thực thi trong các ngôn ngữ lập trình. đồng bộ và không đồng bộ
Mô hình đồng bộ là nơi dòng mã tiếp theo không được thực thi cho đến khi dòng mã hiện tại được thực hiện. Ngay cả khi dòng mã hiện tại gọi API phản hồi trong 500 mili giây hoặc đọc tệp 100 MB, quá trình thực thi sẽ đợi cho đến khi dòng mã hoàn tất. Nói cách khác, trong thực thi đồng bộ, mọi thứ xảy ra lần lượt, cái này nối tiếp cái kia
Mặt khác, nếu có năm dòng mã và dòng thứ hai gọi một API, thì nó sẽ được đẩy xuống nền với cơ chế cho phép thực thi chính biết rằng API đã phản hồi
Trong khi điều đó đang xảy ra, các dòng từ ba đến năm cũng đang được thực thi. Đây là lời giải thích quá đơn giản về mô hình thực thi mã không đồng bộ hoặc không đồng bộ. Trong trường hợp này, mã không chạy từng dòng theo thứ tự. Mọi thứ có thể được đặt ở chế độ nền [hoặc hàng đợi] và sau đó sẽ biết kết quả. Mô hình thực thi này cho phép nhiều việc xảy ra cùng một lúc
Khái niệm về mô hình thực thi là điều cần thiết ở đây bởi vì, tùy thuộc vào ngôn ngữ lập trình, nó có thể hoạt động theo cách đồng bộ hoặc không đồng bộ. Ví dụ: trong Python, bạn có thể viết chương trình theo kiểu đồng bộ hoặc không đồng bộ — trong khi JavaScript là không đồng bộ theo mặc định
Làm sao? . Trước tiên, hãy xem một ví dụ cơ bản, với một thực thi đồng bộ đơn giản hiển thị nhật ký
console.log["first log line"];
console.log["second log line"];
console.log["third log line"];
Đoạn mã trên khá đơn giản và sẽ lần lượt in ba dòng nhật ký, như bên dưới
first log line
second log line
third log line
Bây giờ, hãy làm cho nó thú vị hơn một chút. Xem mã JavaScript không thực thi tuần tự với ví dụ 'chờ 1 giây'
console.log["first log line"];
// 1 second wait
setTimeout[function [] {
console.log["third log line - after 1 second"];
}, 1000];
console.log["second log line"];
Bây giờ, khi đoạn mã trên được thực thi, đầu ra như sau
first log line
second log line
third log line - after 1 second
Vì vậy, những gì xảy ra ở đây? . Nó được đặt thành 1 giây — 1000 ms. Vì vậy, mã này được đẩy xuống nền để được thực thi sau 1 giây
Sau đó, bảng điều khiển
first log line
second log line
third log line
3 được thực thi và thực hiện công việc của nó. Sau 1 giây, first log line
second log line
third log line
4 trong setTimeout thực thi, dẫn đến đầu ra ở trên. Đây là cách mã không đồng bộ hoạt động trong JavaScriptTrong phần tiếp theo, chúng ta sẽ tìm hiểu về các cuộc gọi lại với JavaScript không đồng bộ bằng cách sử dụng một ví dụ thú vị về Github và Twitter
Gọi lại với Async JavaScript
“Gọi lại” là một từ bạn đã từng nghe trong đời thực. bạn gọi cho một người bạn, nhưng bạn của bạn không bắt máy. Bạn đến hộp thư thoại của họ và để lại tin nhắn để họ gọi lại cho bạn. Bạn của bạn nghe thấy tin nhắn và giả sử hai giờ sau, họ gọi lại cho bạn. Khái niệm này giống nhau trong lập trình và JavaScript, đặc biệt là với thực thi không đồng bộ
Chúng tôi sẽ không đi vào chi tiết về các sự kiện và cách các hàm có thể được truyền dưới dạng tham số trong Javascript — đó là một chủ đề nằm ngoài phạm vi của bài viết này. Một điều cần đề cập là các cuộc gọi lại cũng có thể được sử dụng với mã đồng bộ
Để hiểu các cuộc gọi lại trong ngữ cảnh mã không đồng bộ, hãy xem một ví dụ về mã được viết cho trình duyệt. Trong đoạn mã này, bạn sẽ tìm nạp Tweet cuối cùng từ một người chỉ bằng cách sử dụng tên người dùng GitHub của họ
Đầu tiên, bạn sẽ gọi API GitHub để lấy thông tin chi tiết của người dùng, bao gồm cả tên người dùng Twitter của họ. Sau đó, bạn sẽ gọi nguồn cấp dữ liệu RSS của Nitter để nhận tweet cuối cùng của họ
Chúng tôi sẽ không sử dụng API Twitter chính thức vì điều này liên quan đến xác thực và làm phức tạp quá trình. Với mục tiêu rõ ràng của chúng tôi, chúng tôi sẽ sử dụng mã bên dưới [trên JsFiddle để bạn tham khảo]
const request = window.superagent;
const parser = new RSSParser[];
const CORS_PROXY = "//cors-anywhere.herokuapp.com/";
request
.get["//api.github.com/users/abraham"]
.set["Accept", "application/json"]
.end[[err, res] => {
if [!err] {
console.log["Twitter Username: ", res.body.twitter_username];
parser.parseURL[
`${CORS_PROXY}//nitter.net/${res.body.twitter_username}/rss`,
[err, feed] => {
if [!err] {
console.log[
`The last tweet by ${res.body.twitter_username} is - ${feed.items[0].title}`
];
}
}
];
}
}];
console.log["This would log first"];
Bạn đang sử dụng Superagent và trình phân tích cú pháp RSS để hoàn thành tác vụ. Superagent và RSS parser đều hỗ trợ callback và Promises API [sắp tới]. Nếu mã này được viết cho phụ trợ với Node. js, chúng tôi sẽ không cần proxy CORS. Nó là cần thiết cho frontend
Mã bắt đầu bằng việc khởi tạo superagent dưới dạng yêu cầu, RSSParser và proxy CORS. Sau đó, yêu cầu đầu tiên được thực hiện cho người dùng của chúng tôi —
first log line
second log line
third log line
5 — đối với API GitHub. Khi kết thúc yêu cầu, một cuộc gọi lại ẩn danh được thực hiện với first log line
second log line
third log line
6 và first log line
second log line
third log line
7Tên người dùng Twitter chỉ được rút ra khỏi kết quả nếu không có lỗi. Trình phân tích cú pháp RSS đưa ra một yêu cầu khác để phân tích cú pháp URL RSS và nhận các tweet. Cuộc gọi này dẫn đến việc thực hiện một cuộc gọi lại khác
Đây là đầu ra của đoạn mã trên
first log line
second log line
third log line
1Nếu bạn muốn tìm hiểu sâu hơn một chút về cách động cơ V8 xử lý hoạt động không đồng bộ, vui lòng xem bài nói chuyện thú vị 'Giúp tôi bị kẹt trong vòng lặp sự kiện' của Philip Roberts. Anh ấy giải thích vòng lặp sự kiện một cách rõ ràng và ngắn gọn
Tiếp theo chúng ta cùng tìm hiểu về callback hell
Địa ngục gọi lại trong JavaScript
Như đã thấy ở trên, việc viết mã định hướng gọi lại cho các hoạt động không đồng bộ không cảm thấy tự nhiên. Ngoài ra, nếu cần ba lần gọi lại trở lên để hoàn thành một tác vụ, mã của chúng ta sẽ trở nên khó viết, khó hiểu và cuối cùng là quản lý
Vì vậy, khi bạn viết JavaScript theo cách mà quá trình thực thi diễn ra một cách trực quan từ trên xuống dưới với nhiều cấp độ gọi lại phức tạp, bạn sẽ rơi vào địa ngục gọi lại. Một trong những cách dễ nhất để hiểu callback hell là thông qua hình ảnh
Nếu bạn không muốn vào vùng địa ngục gọi lại, bạn có thể sử dụng lời hứa với JavaScript không đồng bộ
↓ Bài viết tiếp tục bên dưới
AppSignal APM cho nút. js
- Thiết lập đơn giản, thông tin chi tiết vô giá
- Phát hiện rò rỉ bộ nhớ và truy vấn N+1
- Giữ lỗi tránh xa người dùng của bạn
Async JavaScript với Promises
Lời hứa [còn được gọi là tương lai trong các ngôn ngữ khác] là các đối tượng biểu thị sự hoàn thành hoặc thất bại cuối cùng của một tác vụ không đồng bộ, dẫn đến một giá trị
Ok, bạn có thể hiểu nó tốt hơn với một phép loại suy khác
Giả sử bạn của bạn hứa sẽ gặp bạn vào cuối tuần. Rồi thứ bảy đến. Nếu người bạn thực sự gặp bạn, lời hứa được "thực hiện". Nếu bạn của bạn không xuất hiện trong cuộc họp, lời hứa bị "từ chối". Cho đến thứ bảy, lời hứa là "đang chờ xử lý"
Khái niệm tương tự có thể giải thích bất kỳ hoạt động không đồng bộ nào — ví dụ: gọi URL/API bên ngoài hoặc đọc tệp từ đĩa
Khi một API được gọi, đối tượng lời hứa đang chờ xử lý cho đến khi có câu trả lời. Nếu mọi việc suôn sẻ, lời hứa được thực hiện và phương thức
first log line
second log line
third log line
8 nhận được kết quả. Trong trường hợp thất bại, phương thức first log line
second log line
third log line
9 được gọi, có đối tượng lỗi. Nếu bạn là một người trực quan, sơ đồ này của tài liệu web MDN có thể giúp bạn hiểu ý tưởng nàyTại thời điểm này, bạn sẽ chuyển đổi mã gọi lại ở trên thành lời hứa. Superagent và RSS Parser đều đã cung cấp API dựa trên lời hứa. Mã để gọi API của GitHub, sau đó lấy tên người dùng Twitter của người dùng
first log line
second log line
third log line
5 [một trong những người dùng GitHub phổ biến ở Hoa Kỳ] và gọi Nitter RSS cho tên người dùng sẽ như thế nàyfirst log line
second log line
third log line
5Nó chủ yếu là cùng một mã từ quan điểm thực thi, nhưng được viết khác nhau. Nó sử dụng lời hứa thay cho cuộc gọi lại ngay bây giờ
Nếu lời hứa không được Superagent và RSS Parser hỗ trợ nguyên bản, thì chúng cũng có thể được viết bằng đối tượng Promise của JavaScript, nhưng bạn không phải tự mình thực hiện. trong nút. js, chúng ta cũng có thể làm điều đó với tiện ích. Chức năng Promisfy — một chủ đề cho một bài viết khác
Có một số điều bạn nên chú ý trong đoạn mã trên. Đầu tiên, để cung cấp tên người dùng Twitter cho các phương thức
first log line
second log line
third log line
8 khác, một biến console.log["first log line"];
// 1 second wait
setTimeout[function [] {
console.log["third log line - after 1 second"];
}, 1000];
console.log["second log line"];
2 được đặt trước console.log["first log line"];
// 1 second wait
setTimeout[function [] {
console.log["third log line - after 1 second"];
}, 1000];
console.log["second log line"];
3 thành một chuỗi trống. Khi giá trị khả dụng, giá trị này được đặt thành giá trị tên người dùng Twitter được sử dụng gần đây nhất vì giá trị này vẫn còn trong phạm viTrong trường hợp có lỗi trong phương thức
first log line
second log line
third log line
8, lỗi sẽ được gửi đến phương thức first log line
second log line
third log line
9, không thực hiện phương thức first log line
second log line
third log line
8 tiếp theoBạn thấy một chuỗi lời hứa ở đây - lời hứa đầu tiên gọi GitHub và lời hứa thứ hai nhận Tweet mới nhất từ Nitter trong chuỗi thứ hai. Điều này có thể gây nhầm lẫn. Chúng ta có thể làm điều này rõ ràng hơn với cú pháp async/await — chúng ta sẽ đề cập đến vấn đề này trong phần tiếp theo
Khi đoạn mã trên được thực thi, nó cho kết quả như bên dưới, không khác với đầu ra gọi lại
first log line
second log line
third log line
1Bạn cũng có thể xem đoạn mã hứa hẹn trên JSFiddle và thử với nó
Nếu lời hứa có vẻ thú vị với bạn, vui lòng tìm hiểu sâu hơn về Lời hứa. tất cả và lời hứa. đua nhau tìm những cách khác để thực hiện lời hứa đồng thời
Bây giờ, hãy tìm hiểu về cách làm việc hiện đại với các lời hứa — sử dụng cú pháp async và await
Không đồng bộ với Đang chờ lời hứa
Không đồng bộ với chờ đợi là cách làm việc hiện đại với những lời hứa theo phong cách rõ ràng hơn nhiều. Async/await có nhiều cú pháp hơn so với lời hứa hơn là một tính năng hoàn toàn mới của ECMAScript
Async cho phép bạn viết mã dựa trên lời hứa như thể nó đồng bộ. Hàm async sẽ luôn trả về một lời hứa, ngầm giúp làm việc với nó dễ dàng hơn. Từ khóa chờ đợi chỉ có thể được sử dụng trong các chức năng không đồng bộ và đợi lời hứa chuyển sang trạng thái hoàn thành. Trạng thái hoàn chỉnh ở đây đề cập đến trạng thái 'đã hoàn thành' hoặc 'bị từ chối'. Cũng có những cuộc thảo luận về sự chờ đợi cấp cao nhất, nhưng nó vẫn chưa trở thành xu hướng chủ đạo.
Với tất cả thông tin đó, bây giờ bạn sẽ chuyển đổi đoạn mã trên bằng lời hứa, sau đó chuyển sang phiên bản chờ async "thoải mái" hơn, như sau
console.log["first log line"];
// 1 second wait
setTimeout[function [] {
console.log["third log line - after 1 second"];
}, 1000];
console.log["second log line"];
3Đoạn mã trên tương tự như đoạn mã hứa hẹn với
console.log["first log line"];
// 1 second wait
setTimeout[function [] {
console.log["third log line - after 1 second"];
}, 1000];
console.log["second log line"];
7 và console.log["first log line"];
// 1 second wait
setTimeout[function [] {
console.log["third log line - after 1 second"];
}, 1000];
console.log["second log line"];
8. Sự khác biệt chính ở đây là tất cả logic được bao bọc trong một hàm không đồng bộ có tên là console.log["first log line"];
// 1 second wait
setTimeout[function [] {
console.log["third log line - after 1 second"];
}, 1000];
console.log["second log line"];
9. Các lời hứa được mở ra với một sự chờ đợi, như từ khóa nói, chờ đợi trước khi chuyển sang dòng tiếp theo. Khi các hoạt động không đồng bộ “hoạt động giống như” mã đồng bộ, các giá trị phụ thuộc vào tác vụ không đồng bộ có thể dễ dàng được gán cho các biến như first log line
second log line
third log line - after 1 second
0. Đó là lý do tại sao try/catch cũng có ý nghĩa hơn ở đâyTrong trường hợp có bất kỳ lỗi nào, vì mã hoạt động giống như mã đồng bộ, nó sẽ bị bắt trong khối bắt. Đối với những lời hứa sử dụng
console.log["first log line"];
// 1 second wait
setTimeout[function [] {
console.log["third log line - after 1 second"];
}, 1000];
console.log["second log line"];
7 và console.log["first log line"];
// 1 second wait
setTimeout[function [] {
console.log["third log line - after 1 second"];
}, 1000];
console.log["second log line"];
8, nếu bỏ qua phần console.log["first log line"];
// 1 second wait
setTimeout[function [] {
console.log["third log line - after 1 second"];
}, 1000];
console.log["second log line"];
8, sẽ bị lỗi. Sử dụng async/await làm cho mã có vẻ đồng bộ, điều này tốt. Tuy nhiên, việc lạm dụng async/await sẽ đánh bại sức mạnh của việc sử dụng ngôn ngữ không đồng bộ như JavaScript, nơi có thể thực hiện "nhiều" việc cùng một lúcĐoạn mã trên đưa ra đầu ra sau, dựa trên tweet cuối cùng của
first log line
second log line
third log line
5first log line
second log line
third log line
1Đoạn mã async/await cũng có sẵn trên Jsfiddle để bạn tham khảo
Bạn đã học được ba cách xử lý mã không đồng bộ trong JavaScript. Trong phần sau, bạn sẽ tìm hiểu cách làm cho mã không đồng bộ JavaScript của bạn hiệu quả hơn
Làm cho mã không đồng bộ JavaScript của bạn hiệu quả hơn
Bạn đã thấy ba biến thể của việc thực hiện hai lệnh gọi HTTP, cái này phụ thuộc vào cái kia. Có một số trường hợp cần xem xét để có thể làm cho mã của bạn hiệu quả hơn
Đầu tiên, hãy sử dụng async/await một cách khôn ngoan. Không code JavaScript như PHP, dùng async/await left, right, center. Điều này sẽ chặn vòng lặp sự kiện
Ví dụ: nếu bạn phải gọi API GitHub cho năm tên người dùng, thì không cần phải gọi từng tên một
Nó có thể được thực hiện đồng thời với
first log line
second log line
third log line - after 1 second
5. Với phương pháp này, hãy cẩn thận để bạn không đạt đến giới hạn tốc độ API, vì các cuộc gọi sẽ được thực hiện đồng thời. Dưới đây là một ví dụ nhanh về first log line
second log line
third log line - after 1 second
5 đang hoạt độngfirst log line
second log line
third log line
0Bạn cũng có thể kiểm tra mã trên JSFiddle. Khi nó chạy, nó cho đầu ra sau
first log line
second log line
third log line
1Câu thần chú chính ở đây là nghĩ về các phần của mã không phụ thuộc vào khối trước đó. Chạy các phần của mã đồng thời, vì vậy các phần này có thể được chia thành các chức năng nhỏ hơn. Công việc được thực hiện nhanh hơn, sử dụng tất cả các tài nguyên có sẵn như CPU và bộ nhớ
Gói [lại
Trong bài đăng này, chúng tôi đã đề cập đến sự khác biệt giữa các mô hình thực thi và mã đồng bộ và không đồng bộ. Sau đó, chúng tôi đã đề cập đến ba cách để xử lý mã không đồng bộ trong JavaScript, sử dụng lệnh gọi lại, lời hứa và async/await [với một ví dụ gọi hai URL]
Cuối cùng, chúng ta đã thấy một ví dụ về cách viết mã không đồng bộ hiệu quả trong Javascript bằng cách sử dụng đồng thời, tách các phần mã độc lập thành các chức năng khác nhau
Mã hóa vui vẻ
P. S. Nếu bạn thích bài đăng này, hãy đăng ký danh sách Phù thủy JavaScript của chúng tôi để tìm hiểu sâu hàng tháng về các mẹo và thủ thuật JavaScript kỳ diệu hơn