Promise đã được mình nhắc đến rất nhiều trong các bài viết liên quan đến Javascript trước đây. Nếu bạn mới bắt đầu học Javascript, hẳn bạn sẽ bật ngay thắc mắc: Promise trong Javascript là gì? Nghe có vẻ như một khái niệm rất trừu tượng phải không?
Promise là một trong “thất trụ” của ngôn ngữ Javascript, mà bạn cần phải nắm rất vững. Nếu bạn chưa biết 7 khái niệm quan trọng đó thì mời bạn đọc bài viết này: 7 khái niệm cơ bản của Javascript.
Việc sử dụng Promise để giải quyết vấn đề callback hell chỉ là một khía cạnh. Promise còn có rất nhiều công dụng hữu ích khác nữa.
Để bạn có một cái nhìn toàn diện hơn về Promise, bài viết này mình sẽ chia sẻ tất cả những kiến thức cần thiết nhất, hướng dẫn Promise trong Javascript.
Nội dung chính của bài viết
#Promise trong Javascript là gì?
Trước hết, mình vẫn nhắc lại định nghĩa promise là gì để các bạn có thể hiểu được bản chất. Theo định nghĩa của nhà phát hành:
Promise là một đối tượng được sử dụng cho tính toán bất đồng bộ. Một promise đại diện cho một tiến trình hay một tác vụ chưa thể hoàn thành ngay được. Trong tương lai, promise sẽ trả về giá trị hoặc là đã được giải quyết (resolve) hoặc là không (reject).
Định nghĩa thì có vẻ khó hiểu vậy thôi. Chứ thực ra, có thể promise đúng như tên gọi của nó: Lời hứa. Tức là một lời hứa có thể thực hiện (resolve) hoặc thất hứa (reject).
Để mình lấy một ví dụ về Promise Javascript: Hãy quay trở lại tuổi thơ dữ dội của mình. Một hôm đẹp trời, bố bảo: “Nếu con ngoan, bố hứa sẽ mua cho một con robot siêu to khổng lồ vào tuần tới”.
Nếu chiểu theo kỹ thuật, thì đây chính là một promise. Và promise này có thể có 3 trạng thái:
- Pending: Hiện lời hứa vẫn đang là hứa suông. Không biết có thành hiện thực hay không?
- Fulfilled: Bố bạn cảm thấy vui và mua cho bạn một con robot thật.
- Rejected: Rất tiếc, bố trượt con đề và lời hứa kia đã thành lời nói gió bay.
#Lập trình bất đồng bộ là gì?
Như định nghĩa của promise, thì khái niệm promise được sinh ra để giải quyết bài toán bất đồng bộ.
Vậy mã bất đồng bộ là gì?
Để hiểu khái niệm này, mình sẽ lật ngược lại vấn đề: mã đồng bộ là gì!
Lập trình đồng bộ tức là bạn có loạt các câu lệnh, block lệnh… Các câu lệnh này sẽ được thực hiện lần lượt, từ trên xuống dưới theo đúng thứ tự bạn viết code. Điều này có nghĩa là câu lệnh sau phải đợi câu lệnh trước hoàn thành.
Như vậy ngược với mã đồng bộ chính là mã bất đồng bộ. Đơn giản vậy thôi!
>>Đọc thêm về JS: Cách tối ưu tốc độ code Javascript
#Các trạng thái của promise Javascript
Cái này mình nhắc lại trong ví dụ ở trên thôi. Tại một thời điểm, promise có thể có một trong 3 trạng thái: Pending, Fulfilled, Rejected.
Cú pháp sử dụng promise chính thống như sau:
var promise = new Promise (function a(resolve, reject) { if(// task complete) { resolve(value); } else { reject(new Error()); } });
#Cách sử dụng Promise trong Javascript
Để dễ hiểu và thực tế, mình sẽ viết code minh họa cho promise sử dụng ví dụ ở đầu bài viết.
let isDadHappy = false; // Định nghĩa một promise let willILetNewToy = new Promise ((resolve, reject) =>{ if(isDadHappy){ resolve(toy); // Fulfilled } else { reject('Bố trượt lô, nên khỏi mua đồ chơi luôn'); } }) // Cách sử dụng promise trên let askDad = ()=>{ willILetNewToy .then(fulfilled =>{ console.log('Mình nhận được một món đồ chơi từ bố'); }) .catch(reject =>{ console.log('Chả có đồ chơi nào cả.'); }) }
Cũng đơn giản phải không?
#Tại sao lại sử dụng promise? Promise trong javascript dùng làm gì?
Trước khi có khái niệm promise, chúng ta biết tới callback. Callback là một function sẽ được thực hiện sau một function thực hiện xong (chính vì vậy mới có tên là callback).
Ủa! Nếu vậy thì promise cũng là callback thôi mà, có khác gì nhau đâu.
Hồi mình mới tìm hiểu về promise cũng có suy nghĩ tương tự như vậy. Vậy promise có điểm gì hay ho hơn callback chứ?
Sau khi tham khảo rất nhiều tài liệu, mình tạm thấy promise có vài ưu điểm như:
- Promise hỗ trợ “chaining”
- Promise hỗ trợ bắt lỗi dễ hơn callback
- Xử lý bất đồng bộ tốt hơn
- Giải quyết được vấn nạn callback hell.
1. Promise hỗ chợ “chaining”
Có một điểm đặc biệt của promise đó là hàm then()
. Giá trị trả về của hàm .then()
lại là một promise. Do vậy, bạn có thể sử dụng promise để gọi liên tiếp nhiều hàm bất đồng bộ.
// Dùng callback xin_mẹ_mua_xe(function(xe) { chở_gái_đi_chơi(xe, function(gái) { chở_gái_vào_hotel(hotel, function(z) { // Làm gì không ai biết }); }); }); // Dùng promise, code gọn nhẹ dễ đọc xin_mẹ_mua_xe .then(chở_gái_đi_chơi) .then(chở_gái_vào_hotel) .then(function() { /*Làm gì không ai biết*/ });
Nhìn đoạn code trên, bạn có thấy code trong sáng hơn so với cách dùng callback không?
2. Bắt lỗi trong Promise
Hàm .catch()
trong promise cũng là một phương thức hoàn toàn giống với .then()
. Tuy nhiên, nó chỉ được dùng khi hàm reject()
được gọi. Tức là nó chỉ dùng khi có lỗi xảy ra.
Ví dụ nhé:
aPromise.catch(doThisWhenItRain); // hoàn toàn giống với aPromise.then(null, doThisWhenItRain);
Hai cách viết trên là hoàn toàn giống nhau về logic. Chỉ khác nhau là viết theo cách 1 thì nó rõ nghĩa hơn thôi.
Điều này có nghĩa hàm .catch()
cũng có thể bắt lỗi liên hoàn hoặc song song giống như hàm .then()
Quay lại đoạn code “xin mẹ mua xe”, chỉ cần một trong 3 hàm bị lỗi là promise sẽ chuyển sang trạng thái reject. Lúc này hàm catch sẽ được gọi.
// Khi một function bị lỗi, promise bị reject (thất hứa) function chở_gái_vào_hotel() { return Promise((response, reject) => { reject("Xin lỗi hôm nay em đèn đỏ"); }); } xin_mẹ_mua_xe .then(chở_gái_đi_chơi) .then(chở_gái_vào_hotel) .then(function() { /*Làm gì đó, ai biết*/ }) .catch(function(err) { console.log(err); //"Xin lỗi hôm nay em đèn đỏ" console.log("xui vl"); });
Ngoài ra, Promise còn giúp giải quyết một hạn chế cơ bản của callback hell (ngoài việc nhìn code dễ đọc hơn), đó là cho phép bắt được tất cả loại lỗi, từ những lỗi throw Error tới lỗi cú pháp lập trình.
Điều này rất cần thiết cho mã nguồn của bạn, tránh bị lọt lỗi.
3. Promises.all()
Trong một số trường hợp, bạn muốn thực hiện và lấy kết quả của nhiều promise cùng một lúc.
Giải pháp đơn giản nhất mà bạn nghĩ tới lúc này là dùng một vòng lặp, duyệt qua tất cả các promise.
const userIds = [1, 2, 3, 4] // api.getUser() là hàm trả về promise const users = [] for (let id of userIds) { api.getUser(id).then(user => ([...users, user])) } console.log(users) // Kết quả in ra là một mảng rỗng: []
Tại sao lại như vậy?
Lý do đơn giản là do mã chạy bất đồng bộ. Khi hàm getUser()
chưa kịp hoàn thành thì hàm console.log()
đã chạy rồi.
Để giải quyết vấn đề này, chúng ta có một giải pháp toàn diện, đó là sử dụng hàm static Promise.all()
.
Phương thức Promise.all([promise1, promise2,...])
nhận vào một mảng các promises, và sẽ chỉ resolve khi và chỉ khi tất cả các promise đã hoàn thành.
Code thì đơn giản như này thôi:
const userIds = [1, 2, 3, 4] Promise.all(usersIds.map(api.getUser)) .then(function(arrayOfResults) { const [user1, user2, user3, user4] = arrayOfResults })
#Tạm kết
Như vậy là chúng ta đã hoàn thành việc tìm hiểu và biết cách sử dụng promise trong Javascript. Đây là một giải pháp quan trọng để bạn thực thi các tác vụ bất đồng bộ trong Javascript.
Gần đây, từ phiên bản ES7, người ta còn khai sinh ra một khái niệm mới đó là async/await, cũng giống như Promise nhưng có nhiều ưu điểm hơn nữa.
Bài viết sau, nếu có thời gian mình sẽ viết riêng về async/await, cũng rất thú vị.
Các bạn nhớ ủng hộ mình nhé, và tiếp tục đón đọc các bài viết tiếp theo của series: Javascript cơ bản nhé.
Bình luận. Cùng nhau thảo luận nhé!