Tìm hiểu Promise trong Javascript - ES6
Promise được đưa vào Javascript từ ES6, đây có thể coi là một kỹ thuật nâng cao giúp xử lý vấn đề bất đồng bộ hiệu quả hơn. Trước đây kết quả của một tác vụ đồng bộ và bất đồng bộ sẽ trả về một kiểu dữ liệu nào đó hoặc thực hiện một Callback Function, điều này quá là bình thường bởi ta đã thấy kể từ ngày bắt đầu học về hàm trong lập trình phải không nào :). Với trường hợp thực hiện Callback Function thì sẽ dễ xảy ra lỗi Callback Hell, nghĩa là gọi callback quá nhiều và lồng nhau nên dẫn đến không kiểm soát được chương trình hoặc bộ nhớ không đủ để hoạt động. Và Trong bài này chúng ta sẽ tìm hiểu về Promise, một package được đưa vào từ ES6 giúp giải quyết vấn đề Callback Hell này.
1. Xử lý đồng bộ trong Javascript?
Đáng lẽ bài viết sẽ đi thẳng vấn đề là Promise luôn nhưng mình cũng nên trình bày sơ lược chút xíu về xử lý đồng bộ.
Javascript là đơn luồng
Javascript là ngôn ngữ chạy một luồng duy nhất, nghĩa là một đoạn mã xử lý một nhiệm vụ sẽ chỉ chạy một lần duy nhất và nếu có lần thứ hai thì nó phải chờ lần thứ nhất kết thúc, điều này tuân thủ theo nguyên tắt hoạt động đồng bộ và hoạt động này đã gây ra phiền toái cho một số trường hợp. Hai người đàn ông cần cắt tóc để đi gặp cùng một người yêu và họ đã vào cắt tại một tiệm Salon duy nhất nằm trong làng, khốn nạn ở chỗ cả hai đến cùng thời điểm nên thợ cắt tóc chỉ có thể cắt một trong hai người mà thôi, người còn lại vui lòng chờ đến lượt. Vậy hành đồng cắt tóc của người thứ nhất đã làm ảnh hưởng đến người thứ hai, và người thứ hai sẽ mất một khoảng thời gian chờ để đến lượt của mình (ta gọi là đồng bộ - Synchronous).
Sự kiện là đa luồng
Sự kiện trong Javascript đã giải quyết vấn đề đa luồng, một hành động sẽ xảy ra khi một sự kiện được kích hoạt. Bạn viết một chương trình gửi mail và bạn gán nó vào sự kiện click vào button, tôi click hai lần liên tiếp thì chương trình sẽ xử lý gửi mail 2 lần cùng một thời điểm, tuy nhiên sẽ rất khó để biết được bên nào sẽ xử lý xong trước.
Bài viết này được đăng tại [free tuts .net]
// Xử lý gửi email function sendEmail() { $.ajax({ url : "some-url", data : {}, success : function(result){ alert('Send Success!'); } }); } // Gán hàm send Email vào sự kiện click $('#button').click(function(){ sendEmail(); });
Liệu event trong Javascript có phải là cách tốt nhất để xử lý vấn đề này?
Sự kiện xử lý rất tuyệt vời cho các trường hợp xử lý nhiều lần trên một đối tượng như bàn phím, chuột, form. Tuy nhiên với sự kiện thì thường ta sẽ không quan tâm đến chuyện gì sẽ xảy ra cho tới khi ta bổ sung một Listener (gửi mail).
// Xử lý gửi email function sendEmail() { $.ajax({ url : "some-url", data : {}, success : function(result){ alert('Send Success!'); }, error : function(eror){ alert('Send Error!'); } }); } // Khi chưa bổ sung listener $('#button').click(function(){ // Tôi chả quan tâm nó làm cái gì cả }); // Khi bổ sung listener // Gán hàm send Email vào sự kiện click $('#button').click(function(){ // Tôi quan tâm đến thao tác gửi email có thành công hay không? // Lúc này phụ thuộc vào hàm callback sendEmail sendEmail(); });
Và việc xử lý các events này là một hành động bất đồng bộ (Async).
Một ví dụ khác là khi load một hình ảnh thì có sự kiện ready()
, ta sẽ áp dụng sự kiện này để đổi hình mặc định nếu như hình không tồn tại.
Note: Các ví dụ dưới đây có sử dụng jQuery.
$('img').ready().then(function() { // loaded }, function() { // failed $(this).attr('src', 'default.png'); });
Và nếu áp dụng Promise trong ES6 thì nó rất đơn giản.
Promise.all([$('img')]).then(function() { // all loaded }, function(img) { $(img).attr('src', 'default.png'); });
Vậy câu hỏi của chủ đề này là làm thế nào để quản lý tốt kết quả của một hành động bất đồng bộ (Async)? Để trả lời câu hỏi này thì ta sẽ nghiên cứu về Promise.
2. Promise trong Javascript - ES6
Vậy promise sinh ra để xử lý kết quả của một hành động cụ thể, kết quả của mỗi hành động sẽ là thành công hoặc thất bại và Promise sẽ giúp chúng ta giải quyết câu hỏi "Nếu thành công thì làm gì? Nếu thất bại thì làm gì?". Cả hai câu hỏi này ta gọi là một hành động gọi lại (callback action).
Như ở ví dụ trên mình demo việc xử lý hành động load hình ảnh của trình duyệt, nếu hình ảnh load không được thì sẽ làm thao tác bổ sung hình mặc định, đây là một hành động callback. Nói trong lập trình thì đây là một callback function.
Khi một Promise được khởi tạo thì nó có một trong ba trạng thái sau:
- Fulfilled Hành động xử lý xông và thành công
- Rejected Hành động xử lý xong và thất bại
- Pending Hành động đang chờ xử lý hoặc bị từ chối
Trong đó hai trạng thái Reject và Fulfilled ta gọi là Settled, tức là đã xử lý xong.
Bây giờ ta sẽ tìm hiểu về cách khởi tạo Promise.
Tạo một Promise
Để tạo một Promise bạn sử dụng cú pháp sau:
var promise = new Promise(callback);
Trong đó callback là một function có hai tham số truyền vào như sau:
var promise = new Promise(function(resolve, reject){ });
Trong đó:
- resolve là một hàm callback xử lý cho hành động thành công.
- reject là một hàm callback xử lý cho hành động thất bại.
Thenable trong Promise
Thenable không có gì to tác mà nó là một phương thức ghi nhận kết quả của trạng thái (thành công hoặc thất bại) mà ta khai báo ở Reject và Resolve. Nó có hai tham số truyền vào là 2 callback function. Tham số thứ nhất xử lý cho Resolve và tham số thứ 2 xử lý cho Reject.
var promise = new Promise(function(resolve, reject){ resolve('Success'); // OR reject('Error'); }); promise.then( function(success){ // Success }, function(error){ // Error } );
Ví dụ: Demo thao tác Resolve và Reject
var promise = new Promise(function(resolve, reject){ resolve('Success!'); }); promise.then( function(success){ console.log(success); } );
Với đoạn code này chạy lên bạn sẽ nhận giá trị là Success!.
Bây giờ ta thử với một đoạn Reject.
var promise = new Promise(function(resolve, reject){ reject('Error!'); }); promise.then( function(success){ console.log(success); }, function(error){ console.log(error); } );
Chạy đoạn code này lên sẽ nhận giá trị là Error!.
Vậy hai hàm callback trong then
chỉ xảy ra một trong hai mà thôi, điều này tương ứng ở Promise sẽ khai báo một là Resolve và hai là Reject, nếu khai báo cả hai thì nó chỉ có tác dụng với khai báo đầu tiên.
var promise = new Promise(function(resolve, reject){ reject('Error!'); resolve('Success!'); }); promise.then( function(success){ console.log(success); }, function(error){ console.log(error); } );
Kết quả:
Chạy lên nó cũng chỉ nhận đúng một giá trị là Error! => callback error đã hoạt động.
Catch trong Promise
then
có hai tham số callbacks đó là success và error. Tuy nhiên bạn cũng có thể sử dụng phương thức catch
để bắt lỗi.
promise.then().catch();
Ví dụ:
var promise = new Promise(function(resolve, reject){ reject('Error!'); }); promise .then(function(message){ console.log(message); }) .catch(function(message){ console.log(message); });
Chạy lên kết quả sẽ là Error!.
Câu hỏi bây giờ đặt ra là nếu ta vừa truyền callback error và vừa sử dụng catch thì thế nào? Câu trả lời nó sẽ chạy hàm callback error và catche sẽ không chạy.
var promise = new Promise(function(resolve, reject){ reject('Error!'); }); promise .then(function(message){ console.log(message); }, function(message){ console.log('Callback Error!'); console.log(message); }) .catch(function(message){ console.log('Catch!'); console.log(message); });
Kết quả mình chụp hình luôn cho các bạn thấy rõ hơn.
3. Lời kết
Vậy Promise là một gói dùng để quản lý kết quả trả về của một hành động Asycn (bất đồng bộ) và nó vừa được bổ sung vào ngôn ngữ Javascript từ version ES6. Việc nắm vững Promise không hề đơn giản và không phải ai cũng hiểu rõ, vì vậy với bài viết này sẽ giúp bạn có cái nhìn tổng quan về Promise trong Javascript nói chung và trong ES6 nói riêng.
Bài này vẫn chưa kết thúc mà vẫn còn phần 2 nữa và mình sẽ trình bày ở bài tiếp theo, mời các bạn theo dõi.