Xử lý bất đồng bộ là một phần cực kỳ quan trọng trong JavaScript. Nếu bạn chỉ học JavaScript cơ bản rồi học Back end thì cũng chưa cần quan tâm lắm chủ đề này, hoặc khi nào cần đến thì lại google tìm cách.
Nhưng nếu bạn đã có ý định học lập trình web, học JS cho đàng hoàng và học cả Front end Framework như học React, Angular, Vue thì chắc chắn bạn cần hiểu rõ về Xử lý bất đồng bộ.
Có nhiều cách để xử lý bất đồng bộ. Nhưng để nắm rõ chủ đề này trong lòng bàn tay thì chúng ta cần đi tìm hiểu lần lượt từ:
Nào, chúng ta sẽ bắt đầu với phần đầu tiên:
I. setTimeout() trong JavaScript
Trong phần này, mình sẽ giúp bạn tìm hiểu về phương thức setTimeout() qua các ví dụ minh họa.
Phương thức setTimeout() thực thi một block code sau thời gian được chỉ định. Phương thức này chỉ thực thi code một lần.
Cú pháp thường được sử dụng của JavaScript setTimeout là:
setTimeout(hamCanThucThi, miliGiay);
Các tham số của hàm setTimeout() là:
-
hamCanThucThi
- một hàm chứa một tác vụ nào đó cần được thực thi
-
miliGiay
- thời gian mà sau đó hàm hamCanThucThi
được thực thi
Hàm setTimeout()
trả về một khoảng intervaID, là một số nguyên dương.
I.1. Ví dụ sử dụng hàm setTimeout() trong JavaScript
Để hiểu cách sử dụng setTimeout() chúng ta sẽ cùng tìm hiểu một vài ví dụ.
Đầu tiên, ví dụ hiển thị lời chào sau 3 giây (tức 3000 mili giây)
// Chương trình hiển thị lời chào
// sử dụng hàm setTimeout()
function chao() {
console.log('Chào bro');
}
// Chạy hàm setTimeout()
setTimeout(chao, 3000);
console.log("Đoạn text này sẽ hiển thị trước");
Kết quả, trong console ta nhận được:
Đoạn text này sẽ hiển thị trước
Chào bro
Trong chương trình trên, hàm setTimeout()
gọi hàm chao()
sau 3000 mili giây (3 giây).
Do đó, chương trình chỉ hiển thị dòng chữ Hello world một lần sau 3 giây.
> Lưu ý: Hàm setTimeout()
sẽ phù hợp khi bạn muốn thực thi một hàm 01 LẦN sau x Giây nào đó. Ví dụ: Hiển thị thông báo cho người dùng sau thời gian đã chỉ định.
Hàm setTimeout() trả về intervalID. Ví dụ:
// Chương trình hiển thị lời chào
// Sử dụng hàm setTimeout()
function chao() {
console.log('Chào bro');
}
let intervalId = setTimeout(chao, 3000);
console.log("ID: " + intervalId);
Kết quả ta nhận được:
ID: 1
Chào bro
Đó là cách thực thi một lần, bây giờ nếu bạn muốn thực thi nhiều lần. Ví dụ, cứ mỗi 3 giây thì hiển thị thời gian hiện tại thì sao?
// Chương trình JS hiển thị thời gian mỗi 3s
// Sử dụng hàm setTimeout()
function xemThoiGian() {
// Trả về ngày giờ
let ngayGio = new Date();
// Trả về thời gian
let thoiGian = ngayGio.toLocaleTimeString();
console.log(thoiGian);
// Hiển thị thời gian sau mỗi 3s
setTimeout(xemThoiGian, 3000);
}
// Gọi phương thức
xemThoiGian();
Kết quả:
14:15:02
14:15:05
14:15:06
........
Vừa nãy có xuất hiện khái niệm intervalID, chắc bạn cũng thắc mắc intervalID để làm gì đúng không?
Vâng, nó được sử dụng để định danh hàm setTimeout()
tương ứng và trong trường hợp bạn muốn dừng nó lại, bạn có thể truyền nó vào hàm clearTimeout()
Ví dụ: Sử dụng hàm clearTimeout()
// Chương trình sử dụng clearTimeout()
// Để dừng setTimeout()
let x = 0;
// Hàm tăng x lên 1 đơn vị
// Và in ra màn hình console
function tangBienX() {
// Tăng x lên 1
x += 1;
console.log(x);
}
// Tăng biến x lên 1 sau mỗi 3s
// và trả về intervalID của setTimeout()
let intervalID = setTimeout(tangBienX, 3000);
// Dừng setTimeout() bằng clearTimeout()
// và truyền intervalID vào
clearTimeout(intervalID);
console.log("setTimeout() đã dừng");
Kết quả:
setTimeout() đã dừng
Trong chương trình trên, hàm setTimeout() được sử dụng để tăng giá trị của biến x
sau 3 giây.
Tuy nhiên, hàm clearTimeout()
đã dừng lệnh gọi hàm của hàm setTimeout()
. Do đó, giá trị x
không được tăng lên.
> Lưu ý: Bạn thường sử dụng phương thức clearTimeout()
khi bạn cần hủy lệnh gọi hàm setTimeout()
TRƯỚC KHI NÓ XẢY RA.
Ngoài cách sử dụng hàm setTimeout()
đơn giản ở trên, bạn cũng có thể chuyển các đối số bổ sung cho hàm setTimeout()
.
Cú pháp là:
// Chương trình hiển thị lời chào theo tên
function chao(ten, tuoi) {
console.log('Chào ' + ten + ' ' + tuoi + ' tuổi');
}
// Truyền đối số vào setTimeout()
setTimeout(chao, 1000, 'Trang', 18);
Kết quả:
Chào Trang 18 tuổi
> Lưu ý: Các tham số sẽ nhận giá trị của các đối số tương ứng. Do đó bạn cần truyền theo đúng thứ tự mà bạn đã khai báo ở hàm trước đó.
II. CallBack Function trong JavaScript
Trong phần này, chúng ta sẽ tìm hiểu về CallBack Function
Để bắt đầu, đây là một ví dụ, một block sẽ thực thi một tác vụ nào đó khi được gọi:
// Chương trình hiển thị lời chào theo tên
function chao(ten) {
console.log('Chào' + ' ' + ten);
}
// Gọi phương thức
chao("Trang");
Chúng ta thấy, trong chương trình trên, một chuỗi được truyền làm đối số cho phương thức chao()
Tuy nhiên, trong JavaScript, bạn cũng có thể truyền một hàm làm đối số cho một hàm khác.
Hàm này được truyền như một đối số bên trong một hàm khác được gọi là CallBack Function (Hàm gọi lại).
Ví dụ:
// Tạo 1 hàm
function chao(ten, callback) {
console.log('Chào ' + ' ' + ten);
callback();
}
// Tạo CallBack Function
function hamCallBack() {
console.log("Đây là hàm CallBack");
}
// Truyền CallBack Function vào hàm chao
chao('Trang', hamCallBack);
Kết quả:
Chào Trang
Đây là hàm CallBack
Chương trình trên có hai hàm:
-
Trong khi gọi hàm
chao()
, chúng ta truyền vào hai đối số (một chuỗi và một hàm).
Hàm hamCallBack()
là một hàm gọi lại.
Lợi ích của CallBack Function
Lợi ích của việc sử dụng CallBack Function là bạn có thể đợi kết quả của một lệnh gọi hàm trước đó và sau đó thực hiện một lệnh gọi hàm khác.
Trong ví dụ sau, chúng ta sẽ sử dụng hàm setTimeout()
để bắt chước chương trình cần thời gian để thực thi, chẳng hạn như chờ dữ liệu đến từ máy chủ.
// Chương trình trì hoãn thực thi
function chao() {
console.log('Chào bro');
}
function chaoTheoTen(ten) {
console.log('Chào' + ' ' + ten);
}
// Gọi hàm
setTimeout(chao, 2000);
chaoTheoTen("Trang");
Kết quả:
Chào Trang
Chào bro
Như bạn đã biết, hàm setTimeout()
thực thi một block code sau thời gian được chỉ định.
Ở đây, hàm chao()
được gọi sau 2000 mili giây (2 giây).
Trong thời gian chờ đợi này, hàm chaoTheoTen("Trang");
được thực thi.
Đó là lý do tại sao Chào Trang
được in trước Chào bro
Đoạn code trên được thực thi bất đồng bộ: Hàm thứ hai chaoTheoTen()
chạy mà không đợi hàm đầu tiên chao()
hoàn thành.
Tuy nhiên, nếu bạn vẫn muốn đợi kết quả của lệnh gọi hàm trước đó trước rồi thực thi hàm tiếp theo, khi đó bạn có thể sử dụng CallBack Function.
Ví dụ:
// Ví dụ Callback Function
function chao(ten, callBack) {
console.log('Chào bro');
// callback function
// executed only after the greet() is executed
callBack(ten);
}
// CallBack Funtion
function chaoTheoTen(ten) {
console.log('Chào' + ' ' + ten);
}
// Gọi hàm sau 2 giây
setTimeout(chao, 2000, 'Trang', chaoTheoTen);
Kết quả:
Chào bro
Chào Trang
Trong chương trình trên, code được thực thi đồng bộ. Hàm chaoTheoTen()
được truyền như một đối số cho hàm chao()
.
Sau 2 giây, hàm setTimeout()
sẽ thực thi hàm chao()
.
Nếu như chương trình trước đã thực thi theo cách bất đồng bộ thì chương trình sau, với việc sử dụng CallBack Function, chúng ta đã có được chương trình thực thi đồng bộ.
> Lưu ý: CallBack Function rất hữu ích trong trường hợp bạn phải đợi kết quả rồi mới thực hiện hành động tiếp theo.
III. Promise và Promise Chaining
Mặc dù CallBack Function có thể xử lý tác vụ bất đồng bộ, nhưng đó không phải là cách tốt nếu như có nhiều CallBack.
Thay vào đó, có một cách tốt hơn và sử dụng Promise.
Promise được sử dụng để tìm hiểu xem tác vụ không đồng bộ có được thực hiện thành công hay không.
Promise là lời hứa và nó có 3 trạng thái.
Hãy lấy một ví dụ trong thực tế bạn hiểu về trạng thái của lời hứa: Bạn cho bạn mình vay tiền và nó hứa trả tiền bạn.
Lúc này, lời hứa có thể xảy ra 3 trường hợp:
Tương ứng, đó là 3 trạng thái của promise:
Một lời hứa bắt đầu ở trạng thái đang Pending (chờ xử lý). Điều đó có nghĩa là quá trình này chưa hoàn tất.
Nếu quá trình thực hiện thành công, nó kết thúc ở trạng thái Fulfilled (hoàn thành). Và, nếu xảy ra lỗi, quá trình sẽ kết thúc ở trạng thái bị Reject (từ chối).
Ví dụ: Khi bạn yêu cầu dữ liệu từ máy chủ bằng cách sử dụng một Promise, nó sẽ ở trạng thái Pending. Khi nhận dữ liệu thành công, nó sẽ ở trạng thái Fulfilled. Nếu lỗi xảy ra, thì nó sẽ ở trạng thái Reject.
III.1. Tạo Promise trong JavaScript
Để tạo một đối tượng Promise, chúng ta sử dụng hàm tạo Promise()
const x = true;
// Tạo một Promise
let traNo = new Promise(function(resolve, reject) {
if (x) {
resolve("Con nợ trả tiền");
} else {
reject("Bùng");
}
});
console.log(traNo);
Kết quả:
promiseObject.then(onFulfilled, onRejected);
Để hiểu cách sử dụng phương thức then() chúng ta cùng làm một ví dụ sau:
// Trả về một promise
let traNo = new Promise(function(resolve, reject) {
resolve('Nợ được trả');
});
// Thực thi sau khi Promise thực hiện thành công
traNo.then(
function thongBao(ketQua) {
console.log(ketQua);
},
)
.then(
function moiVayTiep() {
console.log("Mày vay tiền tiếp không?");
},
);
Kết quả:
Nợ được trả
Mày vay tiền tiếp không?
Trong chương trình trên, phương thức then()
được sử dụng để xâu chuỗi các hàm với Promise.
Phương thức then()
được gọi khi lời hứa được giải quyết thành công.
Bạn cũng có thể xâu chuỗi nhiều phương thức then()
với Promise.
Phương thức catch()
Phương thức catch()
được gọi lại khi Promise bị từ chối (reject) hoặc có lỗi xảy ra:
Ví dụ:
// Trả về một Promise
let traNo = new Promise(function(resolve, reject) {
reject('Bùng');
});
// Thực thi khi Promise thực hiện thành công
traNo.then(
function thongBao(ketQua) {
console.log(ketQua);
},
)
// Thực thi khi xảy ra lỗi
.catch(
function thatHua(ketQua) {
console.log(ketQua);
}
);
Kết quả:
Bùng
Trong chương trình trên, Promise không thực hiện được (reject). Và phương thức catch()
được sử dụng để xử lý lỗi.
Phương thức finally()
Bạn có thể sử dụng phương thức finally()
với promise. Phương thức finally() được thực hiện cho dù Promise có là resolved hay là rejected.
Ví dụ:
// Trả về một Promise
let traNo = new Promise(function(resolve, reject) {
// Có thể là resolved hoặc reject
resolve("Nợ được trả");
});
// Sử dụng phương thức finally()
traNo.finally(
function chaoThanAi() {
console.log("Đéo bao giờ cho vay lại");
}
);
Kết quả:
Đéo bao giờ cho vay lại
Cho vay tiền mà cứ nơm nớp lo sợ như vậy thì đây là hành động cho dù nó có trả nợ hay không trả nợ.
:v. Đùa chút thôi, hãy tin tưởng vào bạn bè của mình bạn ạ.
III.2. Sự khác biệt của Promise và CallBack Function
Promise tương tự như CallBack Function, cả hai đều có thể sử dụng để xử lý bất đồng bộ trong JavaScript
Điểm khác biệt của Promise và CallBack Function như sau:
Đối với Promise:
-
Cú pháp thân thiện và dễ đọc hơn
-
Dễ quản lý, dễ xử lý lỗi hơn
api().then(function(result) {
return api2();
}).then(function(result2) {
return api3();
}).then(function(result3) {
// Làm gì đó
}).catch(function(error) {
// Xử lý nếu có lỗi xảy ra ở phần trước
});
Đối với CallBack Function:
api(function(result) {
api2(function(result2) {
api3(function(result3) {
// Thực hiện công việc
if (error) {
// Làm gì đó
} else {
// Làm gì đó
}
});
});
});
III.3. Một số phương thức khác của Promise
Đối tượng Promise được JavaScript cung cấp vẫn còn một số phương thức hữu ích khác.
Một số như phương thức như resolve()
, reject()
, then()
, catch()
, finally()
bạn đã được học ở trên, đây là số còn lại:
-
all(iterable)
: Chờ tất cả các lời hứa được thực thi thành công hoặc bất kỳ cái nào bị từ chối
-
allSettled(iterable)
: Chờ cho đến khi tất cả lời hứa được giải quyết hoặc bị từ chối
-
any(iterable)
: Trả về giá trị lời hứa ngay sau khi bất kỳ một lời hứa nào được thực hiện
-
race(iterable)
: Chờ cho đến khi bất kỳ lời hứa nào được giải quyết hoặc bị từ chối
Để biết thêm chi tiết về cách sử dụng các phương thức này, bạn có thể xem trên MDN.
IV. Async / Await trong JavaScript
Async / Await là tính năng được giới thiệu trong JavaScript (ES8) để giúp cho việc xử lý bất đồng bộ trở nên dễ dàng hơn.
IV.1. Từ khóa async
Bạn sử dụng từ khóa async
với một hàm để biểu thị rằng hàm đó là một hàm không đồng bộ.
Hàm async trả về một Promise.
Cú pháp của hàm async
là:
// Ví dụ async function
async function f() {
console.log("Async Function");
return Promise.resolve(1);
}
// Gọi hàm
f();
Kết quả:
Async Function
Trong chương trình trên, từ khóa async
được sử dụng trước hàm để biểu thị rằng hàm là không đồng bộ.
Vì hàm này trả về một lời hứa, bạn có thể sử dụng phương thức then()
như sau:
// Ví dụ async function
async function f() {
console.log("Async Function");
return Promise.resolve(1);
}
// Sử dụng hàm then()
f().then(function(ketQua) {
console.log(ketQua);
});
Kết quả:
Async Function
1
Trong chương trình trên, hàm f()
được giải quyết và phương thức then()
được thực thi.
IV.2. Từ khóa await
Từ khóa await
được sử dụng bên trong hàm async để đợi hoạt động không đồng bộ.
Cú pháp để sử dụng await là:
let ketQua = await promise;
Việc sử dụng await
sẽ tạm dừng hàm không đồng bộ cho đến khi Promise trả về kết quả (resolve hoặc reject).
Ví dụ:
// Hứa trả nợ
let traNo = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("Nợ được trả");
}, 4000);
});
// Hàm async
async function asyncCamOn() {
// Đợi cho đến khi Promise thực hiện thành công
let ketQua = await traNo;
console.log(ketQua);
console.log("Cảm ơn!");
}
// Gọi hàm async
asyncCamOn();
Kết quả:
Nợ được trả
Cảm ơn!
Trong chương trình trên, một đối tượng Promise được tạo và nó sẽ được giải quyết sau 4000 mili giây.
Ở đây, hàm asyncCamOn()
là một hàm không đồng bộ async
.
Từ khóa await
sẽ bắt chương trình đợi Promise hoàn thành (resolve hoặc reject) sau đó mới thực thi câu lệnh tiếp theo.
let loiHua1;
let loiHua2;
let loiHua3;
async function asyncFunc() {
let ketQua1 = await loiHua1;
let ketQua2 = await loiHua2;
let ketQua3 = await loiHua3;
console.log(ketQua1);
console.log(ketQua2);
console.log(ketQua3);
}
Trong chương trình trên, await
sẽ chờ đợi từng lời hứa hoàn thành.
IV.3. Xử lý lỗi
Trong khi sử dụng hàm không đồng bộ, bạn viết code theo cách đồng bộ. Và bạn cũng có thể sử dụng phương thức catch()
để bắt lỗi.
Ví dụ:
// Một Promise
let traNo = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("Nợ được trả");
}, 4000);
});
// Hàm async
async function asyncFunc() {
try {
// Đợi Promise được giải quyết
let ketQua = await traNo;
console.log(ketQua);
} catch (error) {
console.log(error);
}
}
// Gọi hàm async
asyncFunc();
Trong chương trình trên, chúng ta đã sử dụng try catch để xử lý các lỗi, nếu gặp phải.
Nếu chương trình chạy thành công, nó sẽ chuyển đến khối try
. Và nếu chương trình ném ra một lỗi, nó sẽ chuyển đến khối catch
.
Để tìm hiểu thêm về chi tiết try catch, hãy đọc hướng dẫn Try Catch trong JavaScript này.
Lợi ích khi sử dụng async:
-
Code dễ đọc hơn so với CallBack Function và Promise
V. Hàm setInterval() trong JS
Trong JavaScript, một block code có thể được thực thi trong các khoảng thời gian xác định. Những khoảng thời gian này được gọi là timing event.
Có hai phương thức để thực thi code trong các khoảng thời gian cụ thể:
setTimeout()
thì bạn đã được tìm hiểu ở phần đầu, nhưng để tập trung vào xử lý bất đồng bộ nên để đến phần này mình mới giới thiệu về hàm setInterval()
V.1. Sử dụng hàm setInterval()
Nếu hàm setTimeout()
thực thi một block code sau khoảng thời gian nhất định thì, hàm setInterval()
thực thi một block code sau mỗi một khoảng thời gian nào đó.
Cú pháp sử dụng đơn giản của hàm setInterval()
là:
// Chương trình sử dụng setInerval()
function chao() {
console.log("Chào bro");
}
setInterval(chao, 1000);
Kết quả:
Chào bro
Chào bro
Chào bro
........
Trong chương trình trên, phương thức setInterval()
gọi hàm chao()
sau mỗi 1000 mili giây (1 giây).
Do đó, chương trình sẽ hiển thị dòng chữ Hello world cứ sau 1 giây một lần.
> Lưu ý: Phương thức setInterval () hữu ích khi bạn muốn lặp lại một hành động nhiều lần. Ví dụ: hiển thị một tin nhắn vào một khoảng thời gian cố định.
Chúng ta sẽ thực hiện thêm một ví dụ khác.
Ví dụ: Hiển thị thời gian sau mỗi 5s
// Chương trình hiển thị ngày & giờ
function xemThoiGian() {
// Trả về đối tượng Date Time
let ngayVaGio = new Date();
// Trả về thời gian
let thoiGian = ngayVaGio.toLocaleTimeString();
// In ra thông tin thời gian
console.log(thoiGian);
}
// Sử dụng hàm setInterval()
// để hiển thị ngày giờ sau mỗi 5s
let intervalID = setInterval(xemThoiGian, 5000);
Kết quả:
16:23:00
16:23:22
16:23:24
16:23:26
16:23:28
........
> Để hiểu hơn về Date Time bạn có thể đọc hướng dẫn Date Time trong JavaScript
V.2. Sử dụng clearInterval()
Như bạn đã thấy trong ví dụ trên, chương trình thực thi một block code tại mọi khoảng thời gian xác định. Nếu bạn muốn dừng lệnh gọi hàm này, thì bạn có thể sử dụng phương thức clearInterval()
.
Cú pháp của phương thức clearInterval() là:
clearInterval(intervalID);
Ở đây, intervalID là giá trị trả về của hàm setInterval()
Ví dụ: Sử dụng clearInterval()
// Chương trình hiển thị thời gian sau mỗi 2s
// sử dụng setInterval()
let x = 0;
// Tạo một hàm
let intervalID = setInterval(function() {
// Tăng x lên 1 đơn vị
x += 1;
// Khi x bằng 5, dừng hàm
if (x === 5) {
clearInterval(intervalID);
}
// Hiển thị thời gian hiện tại
let ngayVaGio = new Date();
let thoiGian = ngayVaGio.toLocaleTimeString();
console.log(thoiGian);
}, 2000);
Kết quả:
16:30:10
16:30:15
16:30:20
16:30:25
16:30:30
Sau 5 lần in thời gian hiện tại ra màn hình console, chương trình đã bị dừng.
Ngoài cách sử dụng đơn giản của hàm setInterval()
ở trên, bạn cũng có thể truyền thêm nhiều đối số vào hàm setInterval()
.
Cú pháp:
setInterval(hamCanThucThi, miliGiay, thamSo1, ....thamSoN);
Ví dụ: Truyền nhiều đối số vào hàm setInterbval()
// Chương trình chào theo tên
function chaoTheoTen(ten) {
console.log('Chào ' + ten);
}
// Truyền tham số vào trong setInterval()
setInterval(chaoTheoTen, 2000, 'Trang');
Kết quả:
Chào Trang
Chào Trang
Chào Trang
..........
Nếu bạn truyền nhiều tham số, các tham số sẽ nhận đối số tương ứng.
> Lưu ý: Nếu bạn chỉ muốn thực thị một lần sau một khoảng thời gian, tốt hơn hết là sử dụng setTimeout()
Tổng kết về Xử lý bất đồng bộ trong JavaScript
Như vậy là qua bài hướng dẫn này mình đã giúp bạn nắm bắt được về chủ đề Xử lý bất đồng bộ trong JavaScript, từ việc thực thi tác vụ sau một khoảng thời gian nhất định đến CallBack Function, Promise và Async / Await.
Đây là một hướng dẫn dài nhưng mình nghĩ rằng nó dễ hiểu và giúp các bạn mới bắt đầu nắm bắt cách xử lý bất đồng bộ dễ dàng hơn.
Hi vọng giúp ích được bạn kha khá trong quá trình HỌC LẬP TRÌNH WEB của bạn.
Nếu bạn còn chưa hiểu lắm, hãy làm lại các ví dụ vài lần, hoặc để lại comment ngay dưới đây nhé. Đừng quên like và chia sẻ lên FB của bạn, bởi biết đâu sau này bạn lại cần đọc lại.
Chúc bạn thành công!
---
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 thực tế + Tuyển dụng ngay!
Đc: Tầng 3, 25T2, N05, Nguyễn Thị Thập, Cầu Giấy, Hà Nội
SĐT: 02435574074 - 0968051561
Email: hello@niithanoi.edu.vn
Fanpage: https://facebook.com/NIIT.ICT/
#niit #niithanoi #niiticthanoi #hoclaptrinh #khoahoclaptrinh #hoclaptrinhjava #hoclaptrinhphp