javascript中的promise和deferred:實踐(二)

介紹:

在第一節呢,我花了大量的時間來介紹promises和deferreds的理論。如今呢,咱們來看看jquery中的promises(做者一下子用單數,一下子用複數形式,妹的)javascript

Note:代碼示例將使用jQuery,儘管它偏離了Promise/A 協議。css

排序模式:

deferred就是一個未完成的對象,promise呢則是一個未知的值。換句話說,prmises/deferreds 容許咱們描述(represent)簡單的任務,能夠很容易地組合來描述複雜的任務和任務流,容許咱們細粒度地控制排序。這就意味着咱們能夠像寫同步代碼同樣去寫異步代碼,so easy,媽媽不再用擔憂個人學習了。此外,promises讓複雜的異步任務變得更容易去抽象成一些小塊的功能--好比動畫加載,動畫處理等等。html

讓咱們來看看三種常見的排序模式,promises使之變成了可能:堆放,並行和順序。java

  • 堆放:同一promise 事件綁定多個處理
    var request = $.ajax(url);
    
      request.done(function () {
          console.log('Request completed');
      });
    
      // Somewhere else in the application
      request.done(function (retrievedData) {
          $('#contentPlaceholder').html(retrievedData);
      });
  • 並行:請求多個promise,返回一個promise,該promise能夠給出執行多個promise的完成狀況。
    $.when(taskOne, taskTwo).done(function () {
          console.log('taskOne and taskTwo are finished');
      });
  • 順序任務:按順序執行任務。
    var step1, step2, url;
    
    url = 'http://fiddle.jshell.net';
    
      step1 = $.ajax(url);
    
      step2 = step1.then(
        function (data) {
            var def = new $.Deferred();
    
            setTimeout(function () {
                console.log('Request completed');
                def.resolve();
            },2000);
    
          return def.promise();
    
      },
        function (err) {
            console.log('Step1 failed: Ajax request');
        }
      );
      step2.done(function () {
          console.log('Sequence completed')
          setTimeout("console.log('end')",1000);
      });

這些模式能夠組合使用或者單獨使用,用以創建複雜的任務或工做流。jquery

常見示例:

許多的promise示例都使用Ajax請求和UI動畫。實際上,jQuery的Ajax請求默認返回的是個promise。這給人形成一種錯覺,覺得解決異步任務完美解決方案就是promise了。其實否則,promise就是一個值得你在任什麼時候候去考慮使用的工具,而不只僅是回調。讓咱們看看使用promise的實例吧。web

  • ajax,這個就算了,一搜一大把,我就略過啦。
  • 定時:建立一個基於timeout的函數:
    function wait(ms) {
          var deferred = $.Deferred();
          setTimeout(deferred.resolve, ms);
    
         // We just need to return the promise not the whole deferred.
         return deferred.promise();
      }
    
      // Use it
      wait(1500).then(function () {
          // Do something brilliant here!
      });
  • 動畫:顯然下面的動畫是沒啥實際用處的,可是這個示例卻給出了promise和動畫如何一塊兒使用。
    var fadeIn = function (el) {
    
          var promise = $(el).animate({
              opacity: 1
          }, 1500);
    
          // Dynamically create and return an observable promise object which will be resolved when the animation completes.
         return promise.promise();
      };
    
    var fadeOut = function(el) {
    
        var promise = $(el).animate({
            opacity: 0
        }, 1500);
    
        // Dynamically create and return an observable promise object
          return promise.promise();
    };
    
    // With the setup out of the way, we can now do one of the following.
    
    // Parallel
    $.when(
        fadeOut('div'), 
        fadeIn('div')
    ).done(function () {
        console.log('Animation finished');
        $('p').css('color', 'red');
    });
    
    // OR
    // Chained
    fadeOut('div').then(function (el) {
        fadeIn(el); // returns a promise
    }).then(function (el) {
        fadeOut(el); // returns a promise
    });
  • 使用$.when()同步並行任務
    var promiseOne, promiseTwo, handleSuccess, handleFailure;
          
            // Promises
            promiseOne = $.ajax({ url: '../test.html' });
            promiseTwo = $.ajax({ url: '../test.html' });
              
              
            // Success callbacks
            // .done() will only run if the promise is successfully resolved
            promiseOne.done(function () {
                console.log('PromiseOne Done');
            });
            
            promiseTwo.done(function () {
                console.log('PromiseTwo Done');
            });
          
            // $.when() creates a new promise which will be:
            // resolved if both promises inside are resolved
            // rejected if one of the promises fails
            $.when(
                promiseOne,
                promiseTwo
            )
            .done(function () {
                console.log('promiseOne and promiseTwo are done');
            })
            .fail(function () {
                console.log('One of our promises failed');
            });
  • 解耦事件和程序邏輯(jsfiddle demo
    var def, getData, updateUI, resolvePromise;
    
    // The Promise and handler
    def = new $.Deferred();
    
    updateUI = function (data) {
        $('p').html('I got the data!');
        $('div').html(data);
    };
    getData = $.ajax({
              url: '/echo/html/', 
              data: {
                  html: 'testhtml', 
                  delay: 3
              }, 
              type: 'post'
        })
        .done(function(resp) {
            return resp;
        })
        .fail(function (error) {
            throw new Error("Error getting the data");
        });
    
    
    // Event Handler
    resolvePromise = function (ev) {
        ev.preventDefault();
        def.resolve(ev.type, this);
        return def.promise();
    };
    
    // Bind the Event
    $(document).on('click', 'button', resolvePromise);
    
    def.then(function() {
        return getData;   
    })
    .then(function(data) {
        updateUI(data);
    })
    .done(function(promiseValue, el) {
        console.log('The promise was resolved by: ', promiseValue, ' on ', el);
    });
    
    
    // Console output: The promise was resolved by: click on <button> </button> 

Gotcha’s: 理解jQuery中的.then()

爲了演示一對「gotcha's」,這些最終的實例將會貫穿個人整個promise實踐。ajax

讓咱們來建立兩個公用函數:shell

// Utility Functions
function wait(ms) {
      var deferred = $.Deferred();
      setTimeout(deferred.resolve, ms);
      return deferred.promise();
}
function notifyOfProgress(message, promise) {
    console.log(message + promise.state());
}

第一次的promise鏈式寫法看起來就像這樣:promise

// Naive attempt at working with .then()

// Create two new deferred objects
var aManualDeferred = new $.Deferred(),
    secondManualDeferred = aManualDeferred.then(function () {
        console.log('1 started');

        wait(3500).done(function () {
            console.log('1 ended');
        });
    });

// After secondManualDeferred is resolved
secondManualDeferred.then(function () {
    console.log('2 started');

    wait(2500).done(function () {
        console.log('2 ended');
    });
});

// Resolve the first promise
aManualDeferred.resolve();

執行的輸出結果:app

1 started
2 started
2 ended
1 ended

納尼?jQuery API不是說 .then()方法能夠鏈式並返回promise麼?我所指望的是不管我在.then()方法中插入了任何代碼,程序都應順序執行,只有上一個任務完成了,才能夠執行下一個。可是這很明顯不是我所指望的結果啊?爲毛啊?

.then()原理

查看jQuery 源代碼,咱們能夠發現:

  • .then()方法始終返回一個新的promise
  • .then()必須傳遞一個函數做爲回調

若是.then()沒有傳遞函數,那麼:

  • 新的promise將會擁有和初始promise行爲一致(這就意味着它當即 被解決/被拒絕)
  • .then()中的輸入將被執行可是會被.then()忽略

若是.then()被傳遞了一個函數,該函數返回了一個promise 對象,那麼:

  • 新的promise將和返回的promise行爲一致
    var deferred = $.Deferred(),
            secondDeferred = deferred.then(function () {
              return $.Deferred(function (newDeferred) {
                setTimeout(function() {
                  console.log('timeout complete');
                newDeferred.resolve();
              }, 3000);
            });
          }),
          thirdDeferred = secondDeferred.then(function () {
              console.log('thirdDeferred');
          });
    
        secondDeferred.done(function () {
            console.log('secondDeferred.done');
        });
        deferred.resolve();
  • 若是.then()傳遞的是一個函數,該函數返回一個值,那麼這個值將成爲新對象的值
    var deferred = $.Deferred(),
            filteredValue = deferred.then(function (value) {
              return value * value;
            });
    
        filteredValue.done(function (value) {
            console.log(value);
        });
    
        deferred.resolve(2); // 4

你可能已經注意到了,爲毛個人版本沒法運行(但是我能運行啊,做者,你腫麼了)。我沒有當即從.then()返回一個promise,因此由.then()建立的新的promise擁有一樣的值。

避免被回調坑爹(Avoiding the descent into callback hell)

咱們知道.then()須要一個回調函數並返回一個promise,因此咱們能夠像下面這麼作:

// Anti-pattern - Return to callback hell

var aManualDeferred = new $.Deferred();

aManualDeferred.then(function () {
    console.log('1 started');

    return wait(3500).then(function () {
        console.log('1 ended');
    }).then(function () {
        console.log('2 started');

        return wait(2500).done(function () {
            console.log('2 ended');
        });
    });
});

// Resolve the first promise
aManualDeferred.resolve();

運行啦。不幸的是,這個回調太shit了,咱們又掉到回調嵌套的坑裏了。還好,咱們有絕招來規避這種嵌套。那麼,如何解決這個問題呢,固然,這得具體狀況具體分析咯。

避免使用大量的無名promise

舉例以下:

// A chain
// Create new deferred objects
var aManualDeferred = $.Deferred();

aManualDeferred.then(function () {
    console.log('1 started');

    // We need to return this, we return a new promise which is resolved upon completion.
    return wait(3500);
})

.then(function () {
    console.log('1 ended');
})

.then(function () {
    console.log('2 started');
    return wait(2500);
})

.then(function () {
    console.log('2 ended');
});

// Resolve the first promise
aManualDeferred.resolve();

此次看起來就漂亮多啦。缺點是,只有一個promise是命名的,不利於咱們細粒度地控制每一個步驟,這個在不少狀況下是很是有用的哦。

解耦promise和處理函數

假如咱們不想深層嵌套,咱們就得命名promise,這樣咱們就有了每一個步驟的控制權。

看看最終版本:

var aManualDeferred, secondManualDeferred, thirdManualDeferred;

// Create two new deferred objects
aManualDeferred = $.Deferred();

secondManualDeferred = aManualDeferred.then(function () {
    console.log('1 started');

    // We need to return this, we return a new promise which is resolved upon completion.
    return wait(3500);
})
.done(function () {
    console.log('1 ended');
});

thirdManualDeferred = secondManualDeferred.then(function () {
    console.log('2 started');
    return wait(2500);
})
.done(function () {
    console.log('2 ended');
});

// Check current state
thirdManualDeferred.notify(
    notifyOfProgress('thirdManualDeferred ', thirdManualDeferred)
);

// Resolve the first promise
aManualDeferred.resolve();

// Console output
// aManualDeferred pending
// secondManualDeferred pending
// 1 started
// 1 ended
// 2 started
// 2 ended

這個優勢就很明顯了,如今的程序分爲三個步驟,咱們能夠訪問每一個promise的狀態,用以發送進程通知,或者在管理代碼執行順序時,也不須要重寫代碼(誰說的,只不過修改代價很小了)。

上下文和傳值

在Ajax示例中,咱們看到,能夠給.resolve()和.fail()函數傳值。若是一個promise resolved(我以爲「resolved」這裏仍是不翻譯的好)了一個值,那麼新的promise就是返回值自己。

var passingData = function () {
    var def = new $.Deferred();

    setTimeout(function () {
        def.resolve('50');
    }, 2000);

   return def.promise();               
};

passingData().done(function (value) {
      console.log(value);
});

當咱們 resolve 了一個promise,咱們能夠給它設置 this。

// Create an object
var myObject = {
    myMethod: function (myString) {
        console.log('myString was passed from', myString);
    }
};

// Create deferred
var deferred = $.Deferred();

// deferred.done(doneCallbacks [, doneCallbacks ])
deferred.done(function (method, string) {
    console.log(this); // myObject

    // myObject.myMethod(myString);
    this[method](string);
});

deferred.resolve.call(myObject, 'myMethod', 'the context');

=> myString was passed from the context

// We could also do this:
// deferred.resolveWith(myObject, ['myMethod', 'resolveWith']);
// but it's somewhat annoying to pass an array of arguments.

// => myString was passed from resolveWith

剩下的是最佳實踐和jquery 中的一些方法介紹,做者一帶而過,我就不翻譯了。想看,就看原文吧。 

ps:

全部的deffered對象都支持的方法:

------------------------

then(doneCallbacks,failCallbacks):由此可知,then能夠處理成功和失敗的回調,這就是和done的區別啊

done(doneCallbacks)

fail(failCallbacks)

---------------

ajax對象還包括

sucess

fail

它們會分別映射到deffered對象的done 和fail方法上

譯自:http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt2-practical-use

相關文章
相關標籤/搜索