Một trong những chìa khóa để lập trình web thành công là có thể thực hiện hàng chục lệnh gọi AJAX trên mỗi trang.
Đây là một thách thức lập trình không đồng bộ điển hình và phần lớn giải pháp bạn chọn đối phó với các lệnh gọi không đồng bộ (asynchronous) sẽ làm hỏng trang web của bạn.
"Đồng bộ hóa các tác vụ không đồng bộ trong JavaScript là một vấn đề nghiêm trọng trong một khoảng thời gian dài"
Vần đề này tạo ra rất nhiều khó khăn cho các các lập trình viên front end và các lập trình viên back-end sử dụng Node.js.
Nhưng lập trình không đồng bộ là một phần công việc hàng ngày của chúng ta vì thế, cho dù bạn muốn tránh cũng không được.
Lịch sử về lập trình không đồng bộ trong JavaScript
Để thực hiện nhiệm vụ lập trình không đồng bộ trong JavaScript, giải pháp đầu tiên và đơn giản nhất là sử dụng các hàm lồng nhau dưới dạng các lệnh Callback.
Tuy nhiên, giải pháp này đã dẫn đến một thứ được gọi là callback hell, và nó có quá nhiều vấn đề.
Sau đó, trong ES6 chúng ta có Promises.
Pattern này giúp code dễ đọc hơn rất nhiều, nhưng nó khác xa với nguyên tắc Không lặp lại chính mình (DRY - Don't Repeat Yourselft).
Vẫn có quá nhiều trường hợp bạn phải lặp lại các đoạn code giống nhau để quản lý đúng luồng ứng dụng.
Và bổ sung mới nhất cho vấn đề này đã được phát hành dưới dạng câu lệnh async / await ở phiên bản ES7.
Cuối cùng có cách lập trình không đồng bộ trong JavaScript dễ đọc và dễ viết như bất kỳ đoạn code nào khác.
Bên dưới đây, hãy cùng xem các ví dụ về từng giải pháp từ callback hell đến async / await để thấy rõ sự phát triển của lập trình không đồng bộ trong JavaScript.
Để làm điều này, chúng ta sẽ kiểm tra một tác vụ đơn giản thực hiện các bước sau:
-
Xác minh tên người dùng và mật khẩu của người dùng.
-
Lấy quyền cho người dùng.
-
Ghi nhật ký thời gian truy cập ứng dụng cho người dùng.
Cách #1: Sử dụng Callback hell (Kim tự thám của sự diệt vong)
Giải pháp cổ lỗ để đồng bộ hóa các cuộc gọi này là thông qua các cuộc gọi lại lồng nhau.
Đây là một cách làm phù hợp cho các tác vụ không đồng bộ đơn giản, nhưng sẽ không mở rộng quy mô được do vấn đề được gọi là callback hell.
Code cho ba tác vụ đơn giản trên sẽ trông giống như sau:
const verifyUser = function(username, password, callback) {
dataBase.verifyUser(username, password, (error, userInfo) => {
if (error) {
callback(error)
} else {
dataBase.getRoles(username, (error, roles) => {
if (error) {
callback(error)
} else {
dataBase.logAccess(username, (error) => {
if (error) {
callback(error);
} else {
callback(null, userInfo, roles);
}
})
}
})
}
})
};
Mỗi hàm nhận một đối số là một hàm khác được gọi với một tham số là phản hồi của hành động trước đó.
Có rất nhiều người sẽ thiếu máu lên não, hoa mắt chóng mặt khi đọc chương trình trên.
Nếu một trang web có hàng trăm đoạn code tương tự thế này thì đến chính người viết ra cũng chịu chết khi maintain.
Ví dụ này thậm chí còn phức tạp hơn khi database.getRoles
là một hàm khác có các lệnh gọi lại lồng nhau.
const getRoles = function(username, callback) {
database.connect((connection) => {
connection.query('get roles sql', (result) => {
callback(null, result);
})
});
};
Ngoài việc code khó bảo trì, nguyên tắc DRY cũng hoàn toàn không có giá trị trong trường hợp này.
Ví dụ: Xử lý lỗi được lặp lại trong mỗi hàm và lệnh callback được gọi từ mỗi hàm lồng nhau.
Đối với các tác vụ không đồng bộ phức tạp hơn, chẳng hạn như lặp qua các lệnh gọi không đồng bộ, nó lại càng khó hơn.
Trên thực tế, không có cách đơn giản nào để hoàn thành nhiệm vụ này nếu bạn sử dụng callback.
Đây là lý do tại sao các thư viện JavaScript Promise như Bluebird và Q lại nhận được rất nhiều sự quan tâm.
Chúng cung cấp một cách để thực hiện các tác vụ không đồng bộ phổ biến mà bản thân ngôn ngữ đó chưa cung cấp (Hoặc phiên bản đó chưa có).
Đó là lúc những Promises xuất hiện chính thức trong phiên bản ES6.
* Promises được đề xuất từ năm 2012 (khái niệm này còn có từ 1976 cơ). Nhưng mãi đến 2015 nó mới chính thức được ECMAScript chấp thuận.
Cách #2: Sử dụng JavaScript Promises
Promises là bước tiến tiếp theo trong việc tìm cách thoát khỏi callback hell.
Phương pháp này không loại bỏ việc sử dụng các lệnh callback, nhưng nó làm cho chuỗi các hàm trở nên dễ đọc và dễ viết hơn nhiều.
Ảnh
Với Promises, code trong ví dụ JavaScript không đồng bộ của chúng ta sẽ trông giống như sau:
const verifyUser = function(username, password) {
database.verifyUser(username, password)
.then(userInfo => dataBase.getRoles(userInfo))
.then(rolesInfo => dataBase.logAccess(rolesInfo))
.then(finalResult => {
// Làm gì đó mà call back sẽ làm
})
.catch((err) => {
// Xử lý lỗi
});
};
Để đạt được kiểu đơn giản này, tất cả các hàm được sử dụng trong ví dụ sẽ phải được Promisised.
Hãy cùng xem phương thức getRoles
sẽ được cập nhật như thế nào để trả về Promise:
const getRoles = function(username) {
return new Promise((resolve, reject) => {
database.connect((connection) => {
connection.query('get roles sql', (result) => {
resolve(result);
})
});
});
};
Chúng ta đã sửa đổi phương thức để trả về một Promise
, với hai lệnh callback và bản thân Promise
thực hiện các hành động từ phương thức.
Bây giờ, giải quyết và từ chối các lệnh callback sẽ được ánh xạ tới các phương thức Promise.then
và Promise.catch
tương ứng.
Bạn có thể nhận thấy rằng phương thức getRoles
vẫn có xu hướng giống callback hell.
Điều này là do cách các phương thức cơ sở dữ liệu được tạo ra do chúng không trả về Promise
.
Nếu các phương thức truy cập cơ sở dữ liệu của chúng ta cũng trả về Promise
thì phương thức getRoles
sẽ giống như sau:
const getRoles = new function(userInfo) {
return new Promise((resolve, reject) => {
database.connect()
.then((connection) => connection.query('get roles sql'))
.then((result) => resolve(result))
.catch(reject)
});
};
Cách #3: Sử dụng Async / Await
Vấn đề callback hell đã được giảm thiểu đáng kể với sự ra đời của Promises. Tuy nhiên, chúng ta vẫn phải dựa vào các lệnh callback được chuyển cho các phương thức then
và catch
của Promise
.
Và Promises đã mở đường cho một trong những cải tiến thú vị nhất trong JavaScript.
ECMAScript 2017 đã mang lại Promise cải tiến dưới dạng câu lệnh async
và await
.
Chúng cho phép chúng ta viết code dựa trên Promise như thể nó đồng bộ, nhưng không chặn luồng chính.
const verifyUser = async function(username, password) {
try {
const userInfo = await dataBase.verifyUser(username, password);
const rolesInfo = await dataBase.getRoles(userInfo);
const logStatus = await dataBase.logAccess(userInfo);
return userInfo;
} catch (e) {
// Xư lý lỗi nếu cần
}
};
Chờ Promise
giải quyết chỉ được phép trong các hàm async
, có nghĩa là verifyUser
phải được xác định bằng cách sử dụng async function
.
Tuy nhiên, khi thay đổi nhỏ này được thực hiện, bạn có thể await
bất kỳ Pomises
nào mà không cần thay đổi bổ sung trong các phương thức khác.
Async - Một lời hứa đã được mong đợi từ lâu
Các hàm không đồng bộ là bước hợp lý tiếp theo trong sự phát triển của lập trình không đồng bộ trong JavaScript. Chúng sẽ làm cho code của bạn sạch hơn và dễ bảo trì hơn nhiều.
Việc khai báo một hàm là async
sẽ đảm bảo rằng nó luôn trả về một Promises
Tại sao bạn nên bắt đầu sử dụng hàm async
trong JavaScript ngay hôm nay?
-
Xử lý lỗi đơn giản hơn nhiều và nó dựa vào try / catch giống như trong bất kỳ code đồng bộ nào khác.
-
Gỡ lỗi đơn giản hơn nhiều. Đặt điểm ngắt bên trong khối
.then
sẽ không chuyển sang khối .then
tiếp theo vì nó chỉ đi qua code đồng bộ. Tuy nhiên, bạn có thể bước qua các cuộc gọi await
như thể chúng là các cuộc gọi đồng bộ.
---
HỌC VIỆN ĐÀO TẠO CNTT NIIT - ICT HÀ NỘI
Học Lập trình chất lượng cao (Since 2002). Học làm Lập trình viên. Hành động ngay!
Đc: Tầng 3, 25T2, N05, Nguyễn Thị Thập, Cầu Giấy, Hà Nội
SĐT: 02435574074 - 0914939543
Email: hello@niithanoi.edu.vn
Fanpage: https://facebook.com/NIIT.ICT/
#niit #niithanoi #niiticthanoi #hoclaptrinh #khoahoclaptrinh #hoclaptrinhjava #hoclaptrinhphp #java #php #python