Làm Việc Với Promises Trong JavaScript
Chào các bạn! Ở bài học trước chúng ta đã tìm hiểu về callback trong JavaScript. Tiếp tục với series hôm nay chúng ta sẽ cùng nhau làm rõ các vấn đề về promises thông qua các ví dụ thực tiễn.
Promises trong JavaScript là gì?
Promise là một cơ trong JavaScript giúp bạn thực thi các tác vụ bất đồng bộ mà không rơi vào tình trạng có quá nhiều các callback lồng vào nhau gây tình trạng callback hell hay pyramid of doom. Thực tế promises là 1 special JavaScript object thể hiện cho sự hoàn thành hoặc thất bại của một tiến trình bất đồng bộ.
Cách sử dụng promises trong JavaScript
Cú pháp:
let promise = new Promise(function(resolve, reject) {
});
Khi promise thực thi hoàn thành công việc, nó sẽ gọi một trong 2 hàm là resolve(value)
và reject(error)
. Giả sử trên kia là một lời cầu hôn của chàng trai dành cho cô gái, ban đầu cô gái hẳn sẽ ngại ngùng còn ngập ngừng (Pending), nhưng sau đó chắc chắn cô gái sẽ đồng ý hoặc từ chối bạn. Hai đối số trên cũng giống như lời tỏ tình vậy. Nếu đồng ý resolve(value)
thì status sẽ là "fulfilled", và result chính là value
. Còn cô gái từ chối reject(error)
lúc này status sẽ là "rejected
" và result
chình là error
.
Để tiếp tục câu chuyện tình yêu ban nãy, bây giờ chúng ta sẽ khởi tạo một promises và thực thi thông qua setTimeout
.
let promise = new Promise(function(resolve, reject) {
// the function is executed automatically when the promise is constructed
// after 1 second signal that the job is done with the result "done"
setTimeout(() => resolve("done"), 1000);
});
Đây chính là trường hợp chàng trai đã đưa ra lời tỏ tình thành công với cô gái, đó hẳn một tin vui. Tuy nhiên cô gái vẫn chưa đưa ra câu trả lời. Ở đoạn code trên, sau một giây “xử lý”, trình thực thi sẽ gọi resolve("done")
để đưa ra kết quả. Điều này thay đổi trạng thái của promise
đối tượng:
Còn nếu chàng trai cũng ngại ngùng nói không nên lời cầu hôn thì sao. Đó chắc chắn là thất bại rồi:
let promise = new Promise(function(resolve, reject) {
// after 1 second signal that the job is finished with an error
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
Tóm lại, người thực thi nên thực hiện một công việc sau đó gọi resolve
hoặc reject
để thay đổi trạng thái của promise
tương ứng. Một promise
được giải quyết resolve
hoặc bị từ chối reject
được gọi là "
settled
"
, nó trái ngược với promise
ban đầu "Pending".
then
Giả sử anh chàng ban nãy đã đưa ra lời tỏ tình thành công với cô gái. Vậy làm sao để chúng ta biết cô gái đó đồng ý hay không. Lúc này giải pháp được đưa ra là sử dụng then
. Nó có cú pháp cơ bản như sau:
promise.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
);
- Đối số đầu tiên của
.then
là một hàm chạy khipromise
được giải quyết và nhận kết quả. - Đối số thứ hai của
.then
là một hàm chạy khipromise
bị từ chối và nhận được lỗi.
Ví dụ: đây là một promise
được giải quyết thành công:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve runs the first function in .then
promise.then(
result => alert(result), // shows "done!" after 1 second
error => alert(error) // doesn't run
);
Còn đây là trường hợp bị từ chối:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// reject runs the second function in .then
promise.then(
result => alert(result), // doesn't run
error => alert(error) // shows "Error: Whoops!" after 1 second
);
Còn nếu bạn muốn cô gái đồng ý luôn thì:
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // shows "done!" after 1 second
Đây là trường hợp bạn chỉ quan tâm đến việc hoàn thành thành công, bạn có thể cung cấp một đối số hàm cho .then
catch
Nếu bạn chỉ quan tâm đến sai sót, bạn có thể sử dụng null
như là đối số đầu tiên: .then(null, errorHandlingFunction)
. Hoặc chúng ta có thể sử dụng .catch(errorHandlingFunction)
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// .catch(f) is the same as promise.then(null, f)
promise.catch(alert); // shows "Error: Whoops!" after 1 second
finally
finally
là một trình xử lý tốt để thực hiện việc dọn dẹp, ví dụ như dừng các loading indicators, vì chúng không còn cần thiết nữa, bất kể kết quả là gì.
Như thế này:
new Promise((resolve, reject) => {
/* do something that takes time, and then call resolve/reject */
})
// runs when the promise is settled, doesn't matter successfully or not
.finally(() => stop loading indicator)
// so the loading indicator is always stopped before we process the result/error
.then(result => show result, err => show error)
- Một
finally
trình xử lý không có đối số. Trongfinally
chúng ta không biết liệupromise
có thành công hay không. - Một
finally
trình xử lý chuyển kết quả và lỗi cho trình xử lý tiếp theo.
Ví dụ, ở đây kết quả được chuyển từ finally
đến then
:
new Promise((resolve, reject) => {
setTimeout(() => resolve("result"), 2000)
})
.finally(() => alert("Promise ready"))
.then(result => alert(result)); // <-- .then handles the result
Còn ở đây có một lỗi trong promise
, nó sẽ chuyển từ finally
qua catch
:
new Promise((resolve, reject) => {
throw new Error("error");
})
.finally(() => alert("Promise ready"))
.catch(err => alert(err)); // <-- .catch handles the error object
Ở bài trước chúng ta đã cùng nhau xây dựng hàm loadScript
để tải 1 tệp. Bây giờ chúng ta sẽ cùng nhau viết lại nó bằng promise
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error(`Script load error for ${src}`));
document.head.append(script);
}
);
}
Kết hợp với then
ta được:
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");
promise.then(
script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
);
promise.then(script => alert('Another handler...'));
Vậy là bài học thứ 2 trong series về promises, async/await
đã kết thúc. Hẹn gặp lại các bạn ở bài học thứ 3 promises chaining
nhé!