PROMISE
Giới thiệu
Tại sao cần dùng promise trong js, khi đó ta cần hiểu về 1 số định nghĩa về sync và async trong js
- Js là một ngôn ngữ đồng bộ sync
- Nhưng nó cũng có 1 số hàm thao tác bất đồng bộ async
VD: setTimeout, setInterval, fetch, XMLHttp Request, file reading, request animation frame
Pain (nỗi đau)
Trong Naruto - hoạt hình Nhật Bản, nhân vật Pain có câu nói: " Những kẻ không hề biết đến nỗi đau thực sự sẽ không bao giờ biết đến hòa bình thực sự."
Thế nên để hiểu rõ về khái niệm promise thì khi không có promise thì khi giải quyết vấn đề (pain) gì thì ta cần dùng promise?
Vấn đề này thường được gọi tên là Callback hell hay JavaScript Pyramid of Doom
Khi nhiều callback lồng nhau, các công việc cần giải quyết phụ thuộc vào nhau, output việc 1 là input cho việc 2,.....Làm code trở nên khó đọc, khó hiểu và khó bảo trì.
Khi đó dùng promise js hỗ trợ xử lý các thao tác bất đồng bộ thay cho callback.
Concept
Cú pháp:
var promise = new Promise(
function(resolve, reject) {
//logic
// resolve: success !
// reject: fail
reject('co loi');
}
promise
.then(function() {
//logic success
})
.catch(function(error) {
console.log(error)
})
.finally(function() {
console.log('proccess done')
})
-
Phần định nghĩa promise sẽ định nghĩa trả về resolve(), reject('co loi') hoặc không gì cả tương ứng với 3 trạng thái bên dưới
-
Khi thực thi :
- Trả về resolve() => thì phần thực thi sẽ chạy vào .then
- Trả về reject() => phần thực thi sẽ chạy vào .catch
- Và có trả về gì đi nữa thì thực thi cũng sẽ chạy vào .finally
-
Khi định nghĩa promise có 3** trạng thái**:
- Pending: promise không resolve hay reject
- Fulfilled: resolve
- Rejected: reject
Chain
Tính chất chuỗi của promise
var promise = new Promise(
function(resolve, reject) {
resolve();
}
promise
.then(function() {
return 1
})
.then(function(data) {
console.log(data);
return data + 1;
})
.then(function(data) {
console.log(data);
})
.catch(function(error) {
console.log(error)
})
.finally(function() {
console.log('proccess done')
})
trong then hay catch, nếu không return ra 1 promise mới thì then thứ 2 sẽ được chạy luôn
Cú pháp có vẻ dễ hiểu và chạy tuần tự từ trên xuống dưới, và không gặp phải vấn đề về callback hell, Nhưng nếu return ra 1 promise mới thì phải chờ promise mới đó chạy xong thì mới chạy tiếp then
let promise = new Promise(
function(resolve, reject) {
resolve();
})
promise
.then(function() {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve([1, 2, 3])
}, 3000)
});
})
.then(function (params) {
console.log(params)
})
.catch(function (err) {
console.log(err);
})
.finally(function () {
console.log('done!');
})
Từ .then thứ 2, sau khi return ra 1 promise ở .then 1 thì .then 2 được hiểu là 1 hành động .then của promise mới vừa return ở trên
Promise method (resolve, reject, all)
promise.resolve
Khi một vào trường hợp có dữ liệu sẵn và muốn sử dụng promise chắc chắn thành công có thể sử dụng
let promise = Promise.resolve('success')
promise
.then((res) => {
console.log('res');
})
promise.reject
Tương tự thế muốn sử dụng promise chắc chắn thất bại có thể sử dụng
let promise = Promise.reject('fauilt')
promise
.catch((err) => {
console.log(err);
})
promise.all
Khi có nhiều promise, có thể chạy các promise này xong xong để đỡ tốn thời gian, cũng như các promise này phải thành công hết thì mới nên dùng promise.all
let promise1 = new Promise((resolve, reject) => {
setTimeout(function() {
resolve([1])
}, 5000)
})
let promise2 = new Promise((resolve, reject) => {
setTimeout(function() {
resolve([2, 3])
}, 3000)
})
let promise = Promise.all([promise1, promise2])
promise
.then(function(result) {
let result1 = result[0];
let result2 = result[1];
console.log(result1.concat(result2))
})
Example
Để làm 1 ví dụ show ra comment đơn giản dùng promise trong thực tế:
-
input:
- api: list users
- api: list comment
-
output: danh sách comment gồm: tên user+nội dung comment
Dữ liệu mảng đầu vào
//input
let users = [
{
id: 1,
name: 'John',
},
{
id: 2,
name: 'smiley',
},
{
id: 3,
name: 'kate',
}
];
let comments = [
{
id: 1,
user_id: 1,
content: 'hom nay ban the nao smiley?',
},
{
id: 2,
user_id: 2,
content: 'Toi on nhe, con ban thi sao John?',
},
{
id: 3,
user_id: 1,
content: 'Toi cung ok hehe!',
}
];
- nội dung html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>hello world</h1>
<ul id="commentBlock"></ul>
<script src="promise.js"></script>
</body>
</html>
- js xử lý
//lay cac user theo mảng userIds
function getUserById(users, userIds) {
return new Promise(function(resolve) {
let result = users.filter(function(user) {
return userIds.includes(user.id);
});
setTimeout(function() {
resolve(result);
}, 1000);
});
}
// Tạo promise trả về comment
function getComments() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(comments)
}, 1000)
});
}
// Thực hiện return ra html comment
getComments()
.then(function(comments) {
//lấy mảng userIds từ danh sách comment
let userIds = comments.map(function(comments) {
return comments.user_id;
});
//return mảng đối tượng gồm chỉ gồm những user có comment và comment đó
return getUserById(users, userIds)
.then(function(dataUsers) {
return {
users: dataUsers,
comments: comments
};
});
})
.then(function(data) {
// lấy elements
let commentBlock = document.querySelector("#commentBlock");
let html = '';
//lặp mảng lấy nội dung comments
data.comments.forEach(comment => {
//lấy ra user có thuộc comment đang lặp
let user = users.find(user => user.id === comment.user_id);
html += `<li>${user.name}: ${comment.content}</li>`;
});
commentBlock.innerHTML = html;
});
và đây là kết quả render
All rights reserved