Chắc chắn khi lập trình, bạn sẽ có các công việc cần thời gian delay (gọi API, lấy dữ liệu từ Database, đọc/ghi file,…). Và đây chính là lúc xử lý bất đồng bộ lên ngôi, hãy cùng mình tìm hiểu về bất đồng bộ trong Javascript và chúng giúp code dễ dàng hơn thế nào nhé.
Trong phần tiên phong này, tất cả chúng ta sẽ cùng khám phá và khái niệm cũng như 1 số ít giải pháp giải quyết và xử lý hay dùng .
Xem Tóm Tắt Bài Viết Này
1. Quá trình đồng bộ (Synchronous)
Đây là một quy trình đã rất quen thuộc với tất cả chúng ta. Về cơ bản thì quy trình này gồm những câu lệnh được thực thi theo thứ tự lần lần lượt, câu lệnh thứ nhất phải hoàn thành xong thì mới hoàn toàn có thể thực thi câu lệnh thứ 2, …
Ví dụ, đây là một đoạn code của quá trình đồng bộ:
Bạn đang đọc: Xử Lý Bất Đồng Bộ Trong Javascript – Phần 1
console.log("job1");
console.log("job2");
console.log("job3");
Các câu lệnh sẽ chạy lần lượt và cho ra tác dụng như sau
job1
job2
job3
Ưu điểm: Do các câu lệnh được chạy lần lượt nên sẽ dễ kiểm soát hơn, ngoài ra nếu có bất kỳ lỗi nào thì chương trình cũng sẽ dừng lại mà không chạy tiếp.
Hạn chế: Đôi khi chúng ta cần lấy dữ liệu từ bên ngoài (đọc file, lấy dữ liệu từ DB, …) nên sẽ cần một thời gian chờ nhất định. Nếu chúng ta thực hiện theo kiểu đồng bộ, thì thời gian chạy của toàn bộ chương trình sẽ bằng tổng thời gian thực hiện từng câu lệnh một
==> Điều này có thể làm giảm hiệu năng của chương trình. Ví dụ ta cần đọc 100 file, mỗi file cần 0.5s ==> Tổng thời gian chạy chương trình sẽ là 50s.
2. Quá trình bất đồng bộ (Asynchronous)
Để giải quyết vấn đề ở quá trình đồng bộ thì chúng ta sẽ sử dụng quá trình bất đồng bộ. Đây là quá trình mà các câu lệnh có thể chạy cùng một lúc chứ không cần chờ câu lệnh trước. Với ví dụ trên, thì ta sẽ chạy đồng thời 100 câu lệnh đọc file cùng một lúc => Chúng ta sẽ chỉ mất khoảng 0.5s đến 1s thay vì 50s như lúc trước.
Một chú ý quan tâm là hoàn toàn có thể câu lệnh thứ 2 sẽ thực thi nhanh hơn câu lệnh 1 nên sẽ trả về hiệu quả sớm hơn. Do đó, tác dụng của những câu lệnh cũng hoàn toàn có thể được trả về không theo thứ tự gọi bạn đâu .
Ưu điểm: Như đã nói, nó giúp chúng ta tối ưu được thời gian chạy của các câu lệnh. Cũng giúp chúng ta thực hiện các tác vụ mất nhiều thời gian mà không làm ảnh hưởng đến luồng chính của chương trình.
Khuyết điểm: Chính vì các câu lệnh được thực hiện đồng thời và kết quả cũng được trả về một cách không theo thứ tự nên sẽ khó kiểm soát cũng như debug code.
3. Các cách xử lý bất đồng bộ phổ biến
Vậy trong Javascript thì làm thế nào để những câu lệnh thực thi theo đúng thứ tự ? ? Mình sẽ nói đến 3 cách giải quyết và xử lý bất đồng bộ hay dùng nhất :
- Callback
- Promise
- Async / Await
3.1 Sử dụng Callback (ES5)
Callback
hiểu đơn giản là bạn truyền một hàm B vào hàm A dưới dạng 1 tham số, một lúc nào đó thì hàm A sẽ gọi hàm B để chạy. Ví dụ:
function asyncFunction(callback) {
console.log("Start");
setTimeout(() => {
callback();
}, 1000);
console.log("Waiting");
}
let printEnd = function() {
console.log("End");
}
asyncFunction(printEnd)
* Ở đây mình dùng setTimeout để giả sử cho thời gian chờ là 1s.
Kết quả khi chạy đoạn code trên:
Start
Waiting
End
Ở đây hàm callback của mình là printEnd
và được truyền vào hàm asyncFunction
dưới dạng 1 tham số. Sau khi chờ 1s thì asyncFunction mới gọi hàm callback để thực hiện các câu lệnh tiếp theo. Callback thường được sử dụng trong các EventListener
để khi bắt được các sự kiện sẽ gọi đến hàm callback.
Và tất nhiên callback cũng có nhược điểm của nó. Nếu như bạn cần thực hiện nhiều câu lệnh bất đồng bộ thì bạn cần phải lồng từng đó callback với nhau, khiến cho code sẽ vô cùng khó đọc, khó debug cũng như phát triển (trường hợp này được gọi là Callback Hell),
Ví dụ:
function getData(link, callback) {
setTimeout(() => {
callback();
}, 1000)
}
getData("Data1", () => {
getData("Data2", () => {
getData("Data2", () => {
getData("Data3", () => {
getData("Data4", () => {
getData("Data5", () => {
getData("Data6", () => {
console.log("Done");
})
})
})
})
})
})
})
3.2 Sử dụng Promise (ES6)
Để giải quyết vấn đề Callback Hell ở trên, phiên bản ES6 đã đem đến cho chúng ta Promise
. Về khái niệm, Promise
chính là “lời hứa” đại diện cho giá trị chưa tồn tại và giá trị đó sẽ được trả về vào một thời gian trong tương lai.
Ví dụ, khi bạn oder một món đồ ở trên mạng và cần 2 ngày để ship về, hoàn toàn có thể thấy hành vi giao hàng ở đây là bất đồng bộ ( cần 2 ngày mới hoàn toàn có thể hoàn thành xong ). Thì chủ shop đã trao cho bạn một ” lời hứa ” đại diện thay mặt cho món hàng đó. Sau đó, bạn vẫn triển khai những hoạt động giải trí khác ( ăn, ngủ, code ) thông thường và sau cuối sẽ nhận được món hàng sau 2 ngày và hoàn toàn có thể sử dụng nó .
Đây là cách để tạo ra một Promise :
let promise = new Promise((resolve, reject) => {
// Asynchronous Code.
});
Promise sẽ nhận vào một hàm callback gồm 2 tham số như sau :
resolve
: một function sẽ được gọi nếu đoạn code bất đồng bộ trong Promise chạy thành công.reject
: một function sẽ được gọi nếu đoạn code bất đồng bộ trong Promise có lỗi xảy ra.
Promise cũng cung ứng cho tất cả chúng ta 2 phương pháp để giải quyết và xử lý sau khi được thực thi :
then():
Dùng để xử lý sau khi Promise được thực hiện thành công (khi resolve được gọi).catch():
Dùng để xử lý sau khi Promise có bất kỳ lỗi nào đó (khi reject được gọi).
Dưới đây là đoạn code hoàn hảo về việc sử dụng Promise :
const randomNumber = new Promise((resolve, reject) => {
const url = 'https://www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new';
let request = new XMLHttpRequest();
request.open('GET', url);
request.onload = function() {
if (request.status == '200') {
resolve(request.response);
} else {
reject(Error(request.statusText));
}
};
request.onerror = function() {
reject(Error('Error fetching data.'));
};
request.send();
});
randomNumber
.then((res) => {
console.log("Success");
console.log("Random number: ", res);
})
.catch((err) => {
console.log("Error: ", err.message);
})
Mình đã khởi tạo một Promise là randomNumber
, nhiệm vụ của Promise này là gọi lên API để lấy một số ngẫu nhiêu trong khoảng [1, 10]. Nếu lấy được số thành công thì sẽ truyền kết quả qua hàm resolve()
, còn nếu có lỗi thì sẽ truyền lỗi qua hàm reject()
.
Ở hàm then()
, mình truyền vào 1 callback để in số đó ra nếu lấy thành công
Còn hàm catch()
thì là callback để thông báo lỗi nếu thất bại.
Ngoài ra, ta cũng có thể nối nhiều Promise với nhau (Promise Chaining) để xử lý nhiều thao tác bất đồng bộ lồng nhau. Từ đó tránh được Callback Hell. Ví dụ:
getAsyncData(url)
.then((res) => {getAsyncData(res.url1)})
.then((res) => {getAsyncData(res.url2)})
.then((res) => {getAsyncData(res.url3)})
.then((res) => {getAsyncData(res.url4)})
.then((res) => {getAsyncData(res.url4)})
.then((res) => {
console.log("Done");
console.log(res);
})
Tạm Kết
Vậy là mình đã giới thiệu với các bạn khái quát về bất đồng bộ cũng như một số cách xử lý hay dùng. Tất nhiên còn 1 cách nữa là Async / Await,
mình sẽ nói rõ hơn ở phần 2 :)) Cảm ơn các bạn đã đọc hết bài viết :))
Source: https://sangtaotrongtamtay.vn
Category: Công nghệ