Promises Chaining Trong JavaScript
Chào các bạn! Ở các bài học trước chúng ta đã biết được trong lập trình bất đồng bộ, việc có quá nhiều các callback lồng nhau (nested callback
) thì sẽ dễ dẫn đến tình trạng callback hell
. Và để giải quyết tình trạng đó chúng ta có thể sử dụng promises
(đã được giới thiệu trong bài Làm việc với promises trong JavaScript). Vậy bạn đã từng thắc mắc nếu một chuỗi các promise được sử dụng liên tiếp thì được gọi là gì không? Và nó sẽ được sử dụng như thế nào? Tất cả những thắc mắc đó sẽ được giải đáp trong bài ngày hôm nay. Chào mừng các bạn đến với bài học Promises Chaining
Promise chaining là gì?
Giả sử promise A
kiểm tra xem có người dùng tồn tại hay không, promise
B thêm người dùng mới, promise C
thực thi điều gì đó bằng cách sử dụng các thông tin người dùng cấp. Nếu người dùng không tồn tại, chúng ta gọi promise B
. Nếu người dùng đã tồn tại, chúng tôi gọi trực tiếp promise C
. Đây chính là promise chaining
.
Cú pháp và cách sử dụng promise chaining
Về cơ bản promise chaining
sẽ trông như thế này:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
Ý tưởng ở đây là kết quả được chuyển qua chuỗi các trình xử lý .then
.
Promise
ban đầu sẽ được giải quyết sau 1 giây(*)
,- Sau đó, các
.then
được gọi(**)
. - Giá trị mà nó trả về được chuyển cho chuỗi các trình xử lí
.then
tiếp theo(***)
- …và cứ như thế.
Khi kết quả được truyền dọc theo chuỗi các trình xử lý, chúng ta có thể thấy một chuỗi các alert
lệnh gọi: 1
→ 2
→ 4
.
Toàn bộ đều hoạt động, bởi vì promise.then
sẽ trả về một promise
, vì vậy chúng ta có thể chuyển tiếp đến .then
tiếp theo.
Một lỗi hay gặp phải ở người mới là chúng ta có thể thêm nhiều .then
vào một promise
duy nhất. Tuy nhiên đây không phải là chuỗi (Chaining). Ví dụ
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
Những gì chúng ta thấy ở đây chỉ là một số xử lý cho một promise
. Chúng không chuyển kết quả cho nhau, thay vào đó chúng được xử lý một cách độc lập.
Đây là hình ảnh (so sánh nó với chuỗi ở trên):
Tất cả .then
trên cùng một promise
nhận được cùng một kết quả - kết quả của promise
đó. Vì vậy, trong đoạn code trên tất cả các alert
hiển thị giống nhau: resolve(1)
.
Returning promises
Một handler (trình xử lí) được sử dụng trong .then(handler)
có thể tạo và trả về một promise
. Trong trường này, các handler tiếp theo sẽ đợi và sau đó nó sẽ nhận được kết quả. Ví dụ
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
Đầu tiên .then
hiển thị 1
và trả về new Promise(…)
trong dòng (*)
. Sau một giây, nó sẽ giải quyết và kết quả ( là đối số của resolve
, đây chính là result * 2
) được chuyển cho trình xử lý thứ 2 của .then
. Trình xử lý đó nằm trong dòng (**)
, nó hiển thị 2
. Vì vậy, đầu ra giống như trong ví dụ trước: 1 → 2 → 4, nhưng bây giờ nó sẽ có độ trễ 1 giây giữa alert
.
Returning promises
cho phép chúng ta xây dựng chuỗi các hành động bất đồng bộ.
Xây dựng function loadScript
Tiếp tục kế thừa từ 2 bài học trước. Bây giờ chúng ta tiếp tục sử dụng funtion loadScript
để tải 1 tập tin.
loadScript("/article/promise-chaining/one.js")
.then(function(script) {
return loadScript("/article/promise-chaining/two.js");
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
// use functions declared in scripts
// to show that they indeed loaded
one();
two();
three();
});
Chúng ta có thể bỏ return
và viết gọn lại như sau:
loadScript("/article/promise-chaining/one.js")
.then(script => loadScript("/article/promise-chaining/two.js"))
.then(script => loadScript("/article/promise-chaining/three.js"))
.then(script => {
// scripts are loaded, we can use functions declared there
one();
two();
three();
});
Ở đây mỗi loadScript
trả về một promise
và lệnh tiếp theo .then
được chạy và giải quyết xong thì nó bắt đầu tải tập lệnh tiếp theo. Vì vậy, các tập lệnh được tải lần lượt và chúng ta có thể thêm nhiều hành động bất đồng bộ hơn vào chuỗi
Về mặt kỹ thuật, chúng ta có thể thêm .then
trực tiếp vào từng function loadScript
, như thế này:
loadScript("/article/promise-chaining/one.js").then(script1 => {
loadScript("/article/promise-chaining/two.js").then(script2 => {
loadScript("/article/promise-chaining/three.js").then(script3 => {
// this function has access to variables script1, script2 and script3
one();
two();
three();
});
});
});
Tổng kết
Với một .then
(hoặc catch/finally
, ...) trình xử lý sẽ trả về một promise
, phần còn lại của chuỗi sẽ đợi cho đến khi nó được giải quyết xong. Khi nó xử lí xong, kết quả (hoặc lỗi) của nó sẽ được truyền. Vậy là bài học thứ 3 trong series tìm hiểu về promises, async/await
đã kết thúc. Hẹn gặp lại các bạn ở bài viết tiếp theo error handling với promises