+1

Tìm hiểu về javascript promise

1. Thế nào là một Javascrip promise.

Một Promise là một method đơn giản để thay thế cho việc chạy, quản lý bất đồng bộ của callback trong javascript. Nó cho phép bạn có thể quản ly asychronous error sử dụng phương pháp tiếp cận try/catch

2. Trạng thái của promise

Tại một thới điểm, môt Promise sẽ 3 trạng thái: Pending, Fulfilled và Rejected

  • pending: Kết quả chưa được xử lý xong, đang chờ
  • fullfilled: Tác vụ được thực hiện thành công
  • rejected: Tác vụ không đồng bộ thất bại

2 trạng thái cuối có thể được gọi chung là settled. một Promise chỉ có thể chuyển trạng thái từ Pending → Settled và không có chiều ngược lại.

Khi khởi tạo một promise , ta sẽ có ngay trạng thái pending. Sau khi chuyển sang settled thỳ promise sẽ giữ nguyên trạng thái đó. Một promise sẽ bị rejected nếu ta gọi hàm rejected hoặc xảy ra một ngoại lệ(exception)

Một promise còn được gọi là thenable(object javascript chứa method then()). Method then() này nhận 2 tham số fullfil và reject theo đúng thứ tự. Cả 2 đều là callback function

ví dụ

Athenable.then(function(data){
   // todo data
   // onFullfill
}, function(error){
   //onReject
});

hàm then có thể được gọi nhiều lần aPromise.then(onFullfill1, onReject1); aPromise.then(onFullfill2, onReject2); ... aPromise.then(onFullfillN, onRejectN);

Promise là kết quả của async nên nó được hiểu là VALUE chứ ko phải là ACTION

3. Sử dụng promise như thế nào

3.1 Trước tiên ta sẽ tìm hiều qua về tác vụ không đồng bộ ( asychronous)

Async là một tác vụ của máy tính mà nó sẽ được hoàn thành trong TƯƠNG LAI GẦN. Async có thể giản lược về 3 trạng thái cơ bản: PENDING, SUCCESS, FAILED.

Khi một async task bắt đầu được thực hiện, nó PHẢI ở trạng thái PENDING.

Sau khi thực hiện xong task, async PHẢI chuyển về 1 trong 2 trạng thái: SUCCESS hoặc là FAILED.

Sau khi đã chuyển về trạng thái SUCCESS hoặc là FAILED, async đó KHÔNG ĐƯỢC PHÉP thay đổi trạng thái nữa.

Async KHÁC Event, nhưng có thể dùng event để phát sự kiện chuyển trạng thái của async. Khi đó, event chuyển trạng thái chỉ được trigger 1 lần. Ví dụ:

 setTimeout(function() {
 ajaxCall('Url', function(data){
    callback()
 });
 }, 2000);

Trong đoạn mã trên hàm callback() là môt callback không đồng bộ, nó sẽ được trả về sau khi ajaxCalll gọi hay nói cách khác trả về sau khi function ngoài cùng return.

Xét thêm 1 ví dụ:

function AAA(xxx, cb){
    data =XXX;
    if (condition){
        cb(data)
    else{
         ajaxCall(xxx, function(data){
         cb(data)
      });
    }

}

Như ví dụ trên ta có 2 hàm cb(). Một hàm dòn 4 sẽ là một callback đồng bộ , callback này trả về trước khi function AAA return, còn trường hợp dòng 7 là một callback không đồng bộ. Nó trả về sau khi function AAA return và sau khi ajaxCall trả về 1 response.

3.2 Sử dụng promise

Do đặc tính của promise là một kết quả của async nên nó được sử dụng rất nhiều cho việc viết callback async cho đẹp.

Một tác vụ muốn delay 5s rồi in ra chữ “LẬP” tiếp đó 3s in ra chú “TRÌNH” sau cùng 2s in ra chữ “JAVASCRIPT” Ta có thể dùng hàm setTimeout để viết như sau:

setTimeout(function(){
   console.log(LP);
   setTimeout(function(){
       console.log(TRÌNH);
       setTimeout(function(){
          console.log(JAVASCRIPT);
      },2000)
   },3000)

}, 5000);

Một hàm như trên có thể chạy đúng những gì yêu cầu đặt ra nhưng nhìn quá rắc rối phải không, chưa kể nếu cần delay 1000 lần thỳ ta cần phải viết lồng nhau 1000 lần → thảm họa. Ta có thể viết gọn hơn chút

function AAA(text, timeout, cb){
    setTimeout(function(){
       console.log(text);
      if (cb){
       cb();
      }
    }, timeout)

}

và gọi

 AAA(LP, 5000, AAA(TRÌNH, 3000, AAA(JAVASCRIPT, 2000)));

Càng khó nhìn nhỉ. 😭

Trong ví dụ trên, timeout là một task, còn function bên trong chính là một task handler. Bản thân task handler lại là một task khác. Nhìn từ góc độ thâm mỹ các callbackk lồng nhau được gọi là hiện tượng “Callback Hell hay Pyramid of Doom.” → nhìn đã thấy xấu xí rồi Rất khó cho việc đọc và debug code nếu có lỗi sảy ra

Rất may ta có thể dùng promise để viết lại đoạn code trên một cách dễ nhìn, dễ hiểu mà ko làm sai lệch yêu cầu như sau // định nghĩa một deferred promise

function deferredTimeout(timeout){
    var defer = Q.defer();
    setTimeout(defer.resolve, timeout);
    return defer.promise;
}

//usage

var xxx = deferredTimeout(5000)

xxx.then(function(){
    console.log('LẬP');
})

.then(function() {
    return deferredTimeout(3000);
})

.then(function() {
    console.log('TRÌNH');
})

.then(function() {
    return deferredTimeout(2000);
})

.then(function() {
    console.log('JAVASCRIPT');
});

Trong ví dụ trên tôi đã dùng thư viện Q của jquery để định nghĩa một deferred. Xuất hiện từ jquery 1.7 trờ lên, Q rất mạnh mẽ cho viêc implement Promise ví nod xây dựng hàm defferred rất dễ dàng như trên

->> Các bạn đã nhìn thấy code dể hiểu và trong sáng hơn trước không? (yaoming)

3.3. Thay đổi giá trị theo trình tự

Một khía cạnh rất mạnh mẽ của promise cho phép ta thay đổi giá trị của một biến sau khi callback function được thực hiện. Ví dụ

   var greetingPromise = sayHello();  // → “hello word”
   greetingPromise.then(function (greeting) {

   return greeting + '!!!!';

   }).then(function (greeting) {

   console.log(greeting);    // 'hello world!!!!’

   });

Ta thấy sau khi gọi hàm then 2 lần biến getting đã bị thay đổi

3.4. Xử lý ngoại lệ

Để sử lý ngoại lệ ta dùng catch để bắt ngọa lệ. catch cũng trả lại cho ta một promise ví dụ:

var greeting;
try {
    greeting = sayHello();
    greeting = addExclamation(greeting);
    console.log(greeting);    // 'hello world!!!!’
} catch(error) {
    console.error('uh oh: ', error);   // 'uh oh: something bad happened’
}

Một lưu ý nữa là catch được gọi ngay cả khi bạn tung ra một ngoại lệ:

aPromise.then(function() { // ngoại lệ sẽ được tung ra
          returnJSON.parse('not a valid json'); })
        .catch(function(error) {       // đừng lo, catch sẽ bắt được      return { message: 'You suck'; }; })
        .then(function(data) { // keep going, man })

### 4 Kết luận

Promise là kết quả mà lời gọi tác vụ không đồng bộ trả về, đại diện cho kết quả của tác vụ không đồng bộ. Ví dụ demo bạn có thể xem tại đây: https://jsfiddle.net/yy7bnfbj/1/ https://gist.github.com/asvenus/b4dda8f2f757f203f53c

Tài liệu tham khảo: http://kipalog.com/posts/Javascript-promise https://spring.io/understanding/javascript-promises


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí