一、前言
為了讓前端們從回呼的地獄中回到天堂, jQuery 也引入了 Promise 的概念。 Promise 是一種令程式碼異步行為更優雅的抽象,有了它,我們就可以像寫同步程式碼一樣去寫非同步程式碼。 jQuery 從1.5版本開始實現了 CommonJS Promise/A 規範這個重量級方案,不過沒有嚴格按照規範進行實現,有一些API上的差異。
好,讓我們來看看他們的特性吧( 本文範例基於jquery 1.8版本以上 )。
二、範例
以前寫動畫時,我們通常是這麼幹的:
$('.animateEle').animate({ opacity:'.5' }, 4000,function(){ $('.animateEle2').animate({ width:'100px' },2000,function(){ // 这样太伤了 $('.animateEle3').animate({ height:'0' },2000); }); });
假如這麼使用回調的話,那就太傷了。幸好,還有一些現成的 Promise 解決方案來優雅地解決這種問題。
我們來看看 jQuery 提供的解決方法。
var animate1 = function() { return $('.animateEle1').animate({opacity:'.5'},4000).promise(); }; var animate2 = function() { return $('.animateEle2').animate({width:'100px'},2000).promise(); }; var animate3 = function(){ return $('.animateEle3').animate({height:'0'},2000).promise(); }; // so easy,有木有,so clear,有木有 $.when(animate1()).then(animate2).then(animate3);
很明顯,更改後的程式碼更易懂易讀了。
但是,上面的程式碼,有些細節的東西並沒有透露,一不小心,就容易出錯,得不到我們想要的順序完成動畫的效果。以下讓我們來全面理解 jQuery 提供的 promise 和 deferred 物件的方法,看看到底如何使用。
三、promise和deffered物件方法
promise 物件其實就是 deferred 物件的特例,因為 promise 物件無法更改非同步狀態,而 deferred 物件可以。這點在他們的方法設計上,有著明顯的體現。
1.promise物件方法
通常,對於DOM,動畫,ajax相關方法,我們都可以使用 promise 方法。呼叫 promise 方法,傳回的是 promise 物件。可以鍊式呼叫 promise 方法。
promise物件常見的方法有三個 : done , fail , then 。
其它的方法就不要去記了, jquery 這裡的接口方法太多了,在我看來挺囉嗦的,就跟早期的事件方法綁定一樣, live , delegate , bind ,最終不是都歸為on 來管了麼。
程式碼範例,如下:
(1)DOM使用 promise 方法:
var box=$('#box'); box.promise().done(function(ele){ console.log(ele);//jQuery box });
(2)Ajax使用 promise 方法(預設回傳一個 promise 對象,所以可以不必明確呼叫 promise 方法):
$.post('/',{}).done(function(data){ console.log('请求成功'); }).fail(function(){ console.log('请求错误'); });
動畫範例已有,就不重複列出了。
2.deferred物件方法
那麼Deferred和Promise之間有什麼差別呢?如你在前面看到的,一個promise就是一個由非同步函數傳回的物件。當你想要自己寫一個這樣的函數時你需要使用一個deferred。
一個deferred物件能做的和一個promise物件差不多,但是它有兩個函數來觸發done()和fail()函數。
一個deferred物件擁有一個resolve()函數來處理一個成功的結果並執行與done()相關的函數。 reject()函數則用來處理失敗的結果並執行與fail()相關的函數。
你可以給resolve()和reject()函數都提供參數,然後它們都會傳遞給與done()和fail()相關的回呼函數。
promise物件沒有resolve()和reject()函數。這是因為你將promise放到了其他的腳本中並且你也不想promise去resolve或reject一個promise。
下面是一個關於deferred的簡單例子。 html只是一個簡單的擁有id屬性為”result”的空div。
$('#result').html('waiting...'); var promise = wait(); promise.done(result); function result() { $('#result').html('done'); } function wait() { var deferred = $.Deferred(); setTimeout(function() { deferred.resolve(); }, 2000); return deferred.promise(); }
其中,wait()函數傳回了一個promise。它將在2s之後被解析。除了setTimeout之外,非同步函數中所有的東西都能這樣使用,例如 動畫,Web worker等等。 wait()函數中的程式碼應該很清晰,我們使用了deferred對象,但是我們傳回了一個限制的promise物件。
對於 deferred 物件呢,也就是使用 $.Deferred() 方法,以及 $.when() 等方法創造出來的對象,有如下的常用方法:
resolve , reject , notify ; done , fail , progress ;
另外還有 promise 、 then 和 always 方法。
之所以這麼排版,是因為他們是對應的,也就是說: resolve 方法會觸發 done 的回調執行, reject 會觸發 fail 的回調, notify 會觸發 progress 的回調。
直接看程式碼:
var wait = function(ms) { var dtd = $.Deferred(); setTimeout(dtd.resolve, ms); // setTimeout(dtd.reject, ms); // setTimeout(dtd.notify, ms); return dtd.promise(); //此处也可以直接返回dtd }; wait(2500).done(function() { console.log('haha,师太,你可让老衲久等了'); }).fail(function() { console.log('失败了'); }).progress(function(res) { console.log('等待中...'); });
我们看到了,上面的代码中,在 wait 函数中,返回的是个 promise 对象,而不是 deferred 对象。
要知道, promise 对象是没有 resolve , reject , notify 等方法的,也就意味着,你无法针对 promise 对象进行状态更改,只能在 done 或 fail 中进行回调配置。所以,你如果这么调用 wait(2500).resolve() 将会报错,因为 wait(2500) 返回的是个 promise 对象,不存在 resolve 方法。
但是,这么做,有个好处,我们把 dtd 这个 deferred 对象放在了 wai t函数中,作为了局部变量,避免了全局的污染;进一步通过 promise 方法,转化 dtd 这个 deferred 对象为 promise 对象,避免了函数 wait 外部可能发生的状态更改(假如我们确实有这个需求)。
比如:
var wait = function(ms) { var dtd = $.Deferred(); setTimeout(dtd.resolve, ms); // setTimeout(dtd.reject, ms); // setTimeout(dtd.notify, ms); return dtd; //此处也可以直接返回dtd }; wait(2500).reject().fail(function(){ console.log('失败了...............'); });
我们在外部更改了 wait 返回的 deferred 对象的状态,这样必然触发该对象的 fail 回调函数。
对于 always 方法,从字面意思上就很容易理解, deferred 对象无论是 resolve 还是 reject ,都会触发该方法的回调。
3.其它共性
此处讲讲 then 和 $.when 方法的使用。它们对 promise 对象也适用。
$.when 方法接受多个 deferred 对象或者纯javascript对象,返回 promise 对象。
then 方法依次接受三个回调,分别为 deferred 对象 resolve , reject , notify 后触发的回调,返回一个 promise 对象。注意,必须传入函数,而该函数只有返回一个 promise 对象,才能够让异步事件按照预期顺序来执行。
我们来看看最开始的动画示例代码, $.when(animate1()).then(animate2).then(animate3) , $.when 方法中接受了一个 animate1 的函数执行结果,也就是得到了一个 promise 对象,而后的 then 中,则只是接受了一个变量名,这样得到的结果是一个匿名的函数体,而该函数中返回的是 promise 对象。正好符合了我们对 then 接受参数的要求。
假如我们把执行语句改成: $.when(animate1()).then(animate2()).then(animate3()) ,这样造成的结果就是三个动画同步执行了。与 $.when(animate1(),animate2(),animate3()) 无异。
既然 then 是如此要求,那么与 then 方法类似的 done , fail , progress 也是一样的。