1. はじめに
フロントエンドをコールバックの地獄から天国に戻すために、jQuery は Promise の概念も導入しました。 Promise は、コードの非同期動作をよりエレガントにする抽象化であり、同期コードを作成するのと同じように非同期コードを作成できます。 jQuery は、バージョン 1.5 以降、強力なソリューションとして CommonJS Promise/A 仕様を実装していますが、仕様に厳密に従って実装されておらず、API にいくつかの違いがあります。
それでは、その機能を見てみましょう (この記事の例は、jquery バージョン 1.8 以降に基づいています)。
2. 例
これまでアニメーションを作成するときは、通常次のようにしていました:
$('.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オブジェクトのメソッドをしっかり理解して使い方を見てみましょう。
3. Promise および遅延オブジェクト メソッド
Promise オブジェクトは非同期状態を変更できませんが、遅延オブジェクトは変更できるため、Promise オブジェクトは実際には遅延オブジェクトの特殊なケースです。これは彼らのメソッド設計に明確に反映されています。
1.promise オブジェクトメソッド
通常、DOM、アニメーション、Ajax 関連のメソッドには、Promise メソッドを使用できます。 Promise メソッドを呼び出すと、Promise オブジェクトが返されます。 Promise メソッドはチェーン内で呼び出すことができます。
Promise オブジェクトには、done、fail、then の 3 つの一般的なメソッドがあります。
他のメソッドについても忘れないでください。jquery には非常に冗長なインターフェイス メソッドがあり、初期のイベント メソッド バインディングと同様に、live、delegate、bind は最終的にすべて分類されるわけではありません。オンがここで対応してくれる?
コード例は次のとおりです:
(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 と Promise の違いは何でしょうか?前に見たように、Promise は非同期関数によって返されるオブジェクトです。このような関数を自分で書きたい場合は、deferred を使用する必要があります。
遅延オブジェクトは Promise オブジェクトと同じことを実行できますが、done() 関数とfail() 関数をトリガーする 2 つの関数があります。
遅延オブジェクトには、成功した結果を処理し、done() に関連する関数を実行するための replace() 関数があります。 request() 関数は、失敗した結果を処理し、fail() に関連する関数を実行するために使用されます。
resolve() 関数とreject() 関数の両方にパラメータを指定すると、それらは両方ともdone() とfail() に関連するコールバック関数に渡されます。
Promise オブジェクトには、resolve() 関数とreject() 関数がありません。これは、Promise を別のスクリプトに入れており、Promise によって Promise が解決または拒否されることを望まないためです。
以下は deferred の簡単な例です。 html は、「result」の id 属性を持つ単純な空の 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 を返します。 2秒後に解析されます。 setTimeout に加えて、アニメーション、Web ワーカーなど、非同期関数のすべてをこの方法で使用できます。 wait() 関数のコードは明確である必要があります。遅延オブジェクトを使用しますが、制限付きの Promise オブジェクトを返します。
遅延オブジェクト、つまり $.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 也是一样的。