在第一節呢,我花了大量的時間來介紹promises和deferreds的理論。如今呢,咱們來看看jquery中的promises(做者一下子用單數,一下子用複數形式,妹的)。javascript
Note:代碼示例將使用jQuery,儘管它偏離了Promise/A 協議。css
deferred就是一個未完成的對象,promise呢則是一個未知的值。換句話說,prmises/deferreds 容許咱們描述(represent)簡單的任務,能夠很容易地組合來描述複雜的任務和任務流,容許咱們細粒度地控制排序。這就意味着咱們能夠像寫同步代碼同樣去寫異步代碼,so easy,媽媽不再用擔憂個人學習了。此外,promises讓複雜的異步任務變得更容易去抽象成一些小塊的功能--好比動畫加載,動畫處理等等。html
讓咱們來看看三種常見的排序模式,promises使之變成了可能:堆放,並行和順序。java
var request = $.ajax(url); request.done(function () { console.log('Request completed'); }); // Somewhere else in the application request.done(function (retrievedData) { $('#contentPlaceholder').html(retrievedData); });
$.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
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! });
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 });
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'); });
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」,這些最終的實例將會貫穿個人整個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()方法中插入了任何代碼,程序都應順序執行,只有上一個任務完成了,才能夠執行下一個。可是這很明顯不是我所指望的結果啊?爲毛啊?
查看jQuery 源代碼,咱們能夠發現:
若是.then()沒有傳遞函數,那麼:
若是.then()被傳遞了一個函數,該函數返回了一個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();
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擁有一樣的值。
咱們知道.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了,咱們又掉到回調嵌套的坑裏了。還好,咱們有絕招來規避這種嵌套。那麼,如何解決這個問題呢,固然,這得具體狀況具體分析咯。
舉例以下:
// 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,這樣咱們就有了每一個步驟的控制權。
看看最終版本:
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