Introduction to ES6 Promises – The Four Functions You Need To Avoid Callback Hell - part 2

Ở bài viết trước trong phần 1, chúng ta đã làm quen với promises, promises là gì, tại sao phải dùng promises và sự khác biệt của promises so với callback truyền thống. Và chúng ta cũng đi qua method đầu tiên new Promises để khởi tạo một đối tượng promises. Trong bài viết này, chúng ta sẽ tiếp tục tìm hiểu các method thú vị khác để tránh vấn đề callback hell.

2. promise.then(onResolve, onReject)

promise.then(onResolve, onReject) cho phép chúng ta gán các handlers đến các event của promise. Tùy thuộc vào những arguments mà bạn cung cấp, bạn có thể handle thành công, thất bại hoặc cả hai.

    // Success handler only
    promise.then(function(details) {
        // handle success
    });

    // Failure handler only
    promise.then(null, function(error) {
        // handle failure
    });

    // Success & failure handlers
    promise.then(
        function(details) { /* handle success */ },
        function(error) { /* handle failure */ }
    );

Chú ý: Đừng cố gắng handle lỗi từ handler onResolve và handler onError trong cùng 1 lệnh then, nó sẽ không làm việc:

    // This will cause tears and consternation
    promise.then(
        function() {
            throw new Error('tears');
        },
        function(error) {
            // Never gets called
            console.log(error)
        }
    );

Nếu đây là tất cả những gì mà promise.then có thì nó thực sự không có ưu điểm gì hơn callback. May mắn thay, còn có nhiều hơn thế, các handler onResolve, onReject đã truyền cho promise.then không chỉ handle kết quả của promise trước, mà chúng còn return trả về một promise mới:

    promise.then always returns a promise

Return một số, string, hay bất kì kiểu gì trị nào:

    // Return a promise which resolves after the specified interval
    function delay(interval) {
        return new Promise(function(resolve) {
            setTimeout(resolve, interval);
        });
    }

    delay(1000)
        .then(function() {
            return 5;
        })
        .then(function(value) {
            console.log(value); // 5
        });

Nhưng điểu quan trọng hơn, nó còn làm việc với cả những promise khác. Return một promise từ method then, handler truyền promise đó thông qua giá trị return của then, điều này cho phép bạn có một chuỗi các promises:

    delay(1000)
        .then(function() {
            console.log('1 second elapsed');
            return delay(1000);
        })
        .then(function() {
            console.log('2 seconds elapsed');
        });

Và bạn có thể thấy, chuỗi promises không còn gây ra một kim tự tháp các space, không còn vấn đề callback hell, nghĩa là code của promise là phẳng.

Và giờ ta có thể sử dụng promises để làm phẳng lại ví dụ animation ở phần 1:

    runAnimation(0);
    setTimeout(function() {
        runAnimation(1);    
        setTimeout(function() {
            runAnimation(2);
        }, 1000);
    }, 1000);
    
    runAnimation(0);
    delay(1000)
        .then(function() {
            runAnimation(1); 
            return delay(1000);
        })
        .then(function() {
            runAnimation(2);
        });

Bạn có thể test lại toàn bộ bằng cách chạy đoạn code JS:

    function runAnimation(step) {
      console.log(step);
    }

    function delay(interval) {
      return new Promise(function(resolve) {
          setTimeout(resolve, interval);
      });
    }

    runAnimation(0);
    delay(1000)
        .then(function() {
            runAnimation(1); 
            return delay(1000);
        })
        .then(function() {
            runAnimation(2);
        });

Promise đến giờ tương đối đơn giản, nhưng có một số điều mà bạn cần lưu ý:

    Rejection handlers in promise.then return resolved promises, not rejected ones.

Thực tế là handler rejection theo mặc định sẽ return một promise resolved, điều này có thể gây ra nhiều phiền toái cho những người mới bắt đầu với promise, hãy xem xet ví dụ sau:

    new Promise(function(resolve, reject) {
        reject(' :( ');
    })
        .then(null, function() {
            // Handle the rejected promise
            return 'some description of :(';
        })
        .then(
            function(data) { console.log('resolved: '+data); },
            function(error) { console.error('rejected: '+error); }
        );

Và kết quả của đoạn code trên là:

    resolved: some description of

Nếu bạn vẫn muốn xử lý các lỗi cho trong handler rejected. Hãy thay thế câu lệnh return giá trị trả về:

    return 'some description of :('

bằng:

    return Promise.reject({anything: 'anything'});

Hoặc cách khác là bạn có thể ném ra một error. Vì

    promise.then turns exceptions into rejected promises

Nghĩa là bạn có thể cho handler raise ra một rejected promise bằng:

    throw new Error('some description of :(')

Cụ thể đoạn logic trên được viết lại như sau:

    delay(1000)
        .then(function() {
            throw new Error("oh no.");
        })
        .then(null, function(error) {
            console.error(error);
        });

3. promise.catch(onReject)

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. Ví dụ:

    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);
            });

4. Promise.all([promise1, promise2, …])

Promise.all chứa các arguments như promise1, promise2, ... Nó sẽ trả về 1 promise kết quả một khi tất cả các promises argument là resolved hoặc tồn tại 1 thằng là rejected.

    The returned promise resolves to an array containing the results of every promise, or fails with the error with which the first promise was rejected

Điều này đặc biệt hữu ích trong bài toán xử lý event cuối cùng khi có một chuỗi các sự kiện song song được hoàn thành trước đó:

    parallel animation 1  \
                                              + - subsequent animation         
    parallel animation 2  /

Ví dụ: Giả sử ta có function nấu_ăn(). Muốn nấu ăn thì ta phải hoàn thành 2 function: về_nhà() và đi_chợ. Mà 2 việc này ta có thể thực hiện cùng một lúc:

    // thay vì
    my_function()
      .then(về_nhà)
      .then(đi_chợ)
      .then(nấu_ăn)
      .catch(console.log);

    // nên viết thế này
    my_function()
      .then(function(){
        return Promise.all([
          về_nhà(),
          đi_chợ()
        ])    
      })
      .then(nấu_ăn)
      .catch(console.error.bind(console));

Cách viết đầu tiên chậm vì muốn đi chợ thì phải về nhà xong, cách viết thứ 2 dùng Promise.all code chạy nhanh gấp nhiều lần so với cách 1 vì về_nhà và đi_chợ sẽ dc chạy song song, cùng lúc ( parallel ) nên chỉ tốn ít time là đã xong. Luồng xử lý được thực hiện song song và sẽ cho perfomance nhanh hơn.

Nguồn: http://jamesknelson.com/grokking-es6-promises-the-four-functions-you-need-to-avoid-callback-hell/