Logo

[Javascript] Promise - Lời hứa ngọt ngào

Javascript là một ngôn ngữ rất hữu dụng nhưng lại hết sức kì quặc. Khi bạn làm việc với Javascript, một trong những điều khó khăn nhất mà bạn sẽ phải đối mặt đó là xử lí các lệnh bất đồng bộ (Asynchronous processing). Tính chất này của Javascript phát sinh bởi vì hầu hết mã JS được xử lí ở phía client – tách biệt với server – do đó không phải bất cứ xử lí dữ liệu nào cũng được đồng bộ ngay xuống CSDL của ứng dụng.

Xử lí bất đồng bộ là gì?
Không giống như các ngôn ngữ phía server – nơi dữ liệu luôn được lấy và xử lí một cách nhanh chóng – Javascript là ngôn ngữ được xử lí hầu hết ở phía client. Điều đó có nghĩa là: mỗi lần bạn muốn lấy một dữ liệu nào đó bạn phải gửi yêu cầu (request) về phía server và phải đợi server phản hồi.

Trong quá trình đợi server trả về dữ liệu, có những xử lí cần phải được tiếp tục chạy, một số xử lí khác thì cần phải đợi cho tới khi dữ liệu được phản hồi về client mới có thể kích hoạt, do ta không biết được chính xác khi nào thì dữ liệu đã được xử lí ở client sẽ đồng bộ với phía server, vì vậy ta gọi các xử lí có tính chất này là: xử lí bất đồng bộ (giữa client và server).

Vậy thì Promise là gì?
Để xử lí các lệnh bất đồng bộ, ta có thể dùng cơ chế gọi hàm callback, tuy nhiên không phải lúc nào ta cũng có thể áp dụng được kĩ thuật này, và không phải lúc nào kĩ thuật này cũng tốt. Một trong những kĩ thuật khác mà Javascript hỗ trợ đó là sử dụng Promise.

Ta hãy xem xét 1 ví dụ minh hoạ trong thực tế để dễ mường tượng nhé. Hình dung một cách ngắn gọn như sau:

Bạn tỏ tình với một cô gái, và cô ấy hứa (promise) là sẽ trả lời bạn trong một ngày nào đó vào tuần sau.

Đây là một promise điển hình, một promise sẽ có 3 trạng thái (state) như sau:

Promise đang pending: bạn sẽ không biết được kết quả cho tới khi cô ấy trả lời vào tuần sau.
Promise được trả lời và chấp nhận – trạng thái resolved: cô ấy cũng yêu bạn, cô ấy trả lời bạn rằng cô ấy cũng yêu bạn và đồng ý làm người yêu của bạn. (quá tuyệt vời)
Promise được trả lời và từ chối – trạng thái rejected: cô ấy trả lời bạn và nói rằng cho ấy chỉ xem bạn như một người anh trai tốt.
Chúng ta cùng xem việc tạo và sử dụng một promise trong Javascript thì như thế nào nhé.

Khởi tạo 1 promise
Chuyển thể từ ngôn ngữ nói sang Javascript, giả sử tâm trạng của cô ấy sẽ quyết định tới câu trả lời:

var isSheHappy = false;

// Promise
var willBeCouple = new Promise(
   function (resolve, reject) {
      if (isSheHappy) {
         var answer = {feedback: "I accept to be your girlfriend"};
         resolve(answer); // fulfilled
      } 
     else {
        var reason = new Error('I do not love you');
        reject(reason); // reject
     }
   }
);

Đoạn code trên đã khá rõ ràng:

Biến boolean isSheHappy để xác định cô ấy có vui khi bạn tỏ tình hay không.
Bạn có một promise là willBeCouple, biến này có thể có trạng thái là resolved (nếu cô ấy đồng ý lời tỏ tình) hoặc rejected (nếu cô ấy từ chối bạn).
Nói tóm lại, việc khai báo sử dụng một promise sẽ có cấu trúc như sau:

new Promise(function (resolve, reject) {
   //solve or reject here
});

Tham số resolve và reject là 2 hàm callback được định nghĩa sẵn trong promise.

Trong trường hợp xử lí kết quả là thành công, bạn gọi resolve() với ý nghĩa là promise được xử lí thành công. Ngược lại, nếu kiểm tra và kết quả là fail, bạn có thể gọi reject() để báo hiệu xử lí bên trong promise bị lỗi.

Xử lí một promise – Chạy tiếp giả định vừa đặt ra
Ở phía trên, bạn đã hình dung được một promise nó là như thế nào, bây giờ chạy thử xem sao nhé:

// call our promise
var askYourGirlFriend = function () {
   willBeCouple
     .then(function (fulfilled) {
         // yeah, she accepted your proposal
         console.log(fulfilled); // output: {feedback: 'I accept to be your girlfriend'}
      })
      .catch(function (error) {
         // oops, she refused it
         console.log(error.message); // output: 'I do not love you'
      });
};

askYourGirlFriend();

Đoạn code trên có gì nào?

Bạn khai báo một hàm là askYourGirlFriend(), trong hàm này cũng ta sẽ xử lí lệnh willBeCouple.
Để xử lí kết quả promise, chúng ta dùng then() và catch() trong hai trường hợp thành công và thất bại tương ứng.
Tham số fullfilled khi xử lí ởthen() chính là kết quả trả về khi promise được resolved, trong trường hợp này đó chính là biến answer. Tương tự, tham số error trong xử lí catch() chính là kế quả trả về khi promise bị rejected, trong trường hợp này là biến reason.
Tính chất hữu ích của Promise - Tính chainable
Một tính chất cũng khá hữu dụng của promise là nó có thể chainable, tức là có thể xử lí nhiều promise nối tiếp nhau. (xin lỗi mình không biết dịch ra tiếng việt thế nào, thôi thì để tiếng anh cho dễ hiểu). Hình dung như sau:

Bạn bè của bạn muốn biết bạn tỏ tình thành công hay không? Khi đó, bạn hứa sẽ thông báo với bạn bè về kết quả lần tỏ tình của mình sau khi nhận được câu trả lời từ bạn gái.

Việc này được triển khai như sau, chúng ta sẽ tạo thêm một promise nữa:

// 2nd promise
var showOff = function (answer) {
   return new Promise(
      function (resolve, reject) {
         var message = 'Hey friend, she accepted. This is the result: ' + answer.feedback;
         resolve(message);
      }
   );
};

Chú ý: ta không nhất thiết phải xử lí hết cả 2 trường hợp resolve hoặc reject.

Bây giờ chúng ta sẽ nối promise, chúng ta sửa lại hàm askYourGirlFriend() một chút, ý định của chúng ta bây giờ là khi cô ấy trả lời là mình sẽ báo kết quả luôn cho bạn bè biết, việc đó được triển khai như sau:

// call our promise
var askYourGirlFriend = function () {
    willBeCouple
        .then(showOff) // chain it here
        .then(function (fulfilled) {
           console.log(fulfilled);
           // output: 'Hey friend, she accepted. This is the result: I accept to be your girlfriend.'
        })
        .catch(function (error) {
           // oops, she refused it
           console.log(error.message);
           // output: 'I do not love you'
        });
};

Đơn giản phải không nào! :smile:

Vậy thì promise có tính asynchronous ở chỗ nào?
Promise có tính asynchronous, để chứng minh điều đó, chúng ta thử nghiệm như sau:

// call our promise
var askYourGirlFriend = function () {
    console.log("before promise!");

    willBeCouple
       .then(showOff) // chain it here
       .then(function (fulfilled) {
          console.log(fulfilled);
          // output: 'Hey friend, she accepted. This is the result: I accept to be your girlfriend.'
       })
       .catch(function (error) {
           // oops, she refused it
           console.log(error.message);
           // output: 'I do not love you'
       });

    console.log("after promise!");
};

askYourGirlFriend();

Thử đoán xem kết quả là gì nào? Nhiều người sẽ nghĩ là kết quả sẽ thế này:

before promise!
Hey friend, she accepted. This is the result: I accept to be your girlfriend.'
after promise!

Nhưng … kết quả ở trên là SAI nhé, kết quả đúng phải là:

before promise!
after promise!
Hey friend, she accepted. This is the result: I accept to be your girlfriend.'

Để lí giải cho điều này, bạn hãy hình dung: Bạn đâu có “bất động” trong lúc bạn đang chờ câu trả lời của cô người yêu phải không nào? Code cũng thế, các xử lí sẽ tiếp tục được chạy mà không phải chờ kết quả (đời vẫn trôi đi khi bạn đợi câu trả lời). Bất cứ xử lí nào bạn muốn làm sau khi có kết quả của promise thì bạn đặt nó vào trong khối lệnh then(). Đây là một kiểu xử lí mà chúng ta gọi là “bất đồng bộ” (asynchronous).


Tôi là Thành

about me

Blog

Chia sẻ kiến thức Lập Trình và Tiếng Nhật

Code