1. はじめに
jQuery 1.5 より前は、複数の Ajax 操作が必要な場合、一般に次の 2 つの方法を使用していました。
1).Ajax のシリアル呼び出し
$.ajax({ success: function() { $.ajax({ success: function() { $.ajax({ //callbacks... }); }); });
このメソッドのコードは読みにくく、非効率で、わかりにくく、デバッグやトラブルシューティングが複雑です。
2) Ajax を並列で呼び出します
var promises = []; $.ajax({ success: function() { promises.push('resolved'); check(); } }); $.ajax({ success: function() { promises.push('resolved'); check(); } }); $.ajax({ success: function() { promises.push('resolved'); check(); } }); var check = function() { //checks for all 3 values in the promises array }
このメソッドは、コールバック関数の呼び出しに非常に適しており、並列でデータを取得し、可読性も優れています。欠点は、コードが長いこと、スケーラビリティが低いこと、デバッグとトラブルシューティングが非常に複雑であることです。
jQuery 1.5 以降、遅延オブジェクトが追加されました。したがって、上記と同じ要件は次の方法で実現できます。
1) 約束
var address = $.ajax({}); var tweets = $.ajax({}); var facebook = $.ajax({}); render_side_bar = function(address, tweets, facebook){ //render sidebar } render_no_side_bar = function () { } $.when(address, tweets, facebook).then(render_side_bar, render_no_side_bar)
コードは可読性が高く、スケーラビリティが高く、デバッグとトラブルシューティングの複雑さが大幅に軽減されていることがわかります。
そこで問題は、Promise と Deferred オブジェクトとは一体何なのかということです。
2. 詳しい説明
2. 遅延オブジェクトとは何ですか?
遅延オブジェクトは、jQuery バージョン 1.5 で導入されたコールバック関数ソリューションであり、完了する特定の操作を表し、ユーザーがそれを使用するのに役立ついくつかのメソッドを提供します。
遅延オブジェクトは、Promises インターフェースの実装です。 jQuery 1.5 以降のすべての Ajax バージョンによって返される jqXHR オブジェクトは遅延オブジェクトです。
2. 遅延オブジェクトのいくつかの利点
2.1. 同じ操作に複数のコールバック関数を指定する
遅延オブジェクトの利点の 1 つは、従来の Ajax では不可能だった複数のコールバック関数を操作に追加できることです。
$.ajax("test.html") .done(function(){ alert("first success callback!");} ) .fail(function(){ alert("there is an error!"); } ) .done(function(){ alert("second success callback!");} );
2.2. 複数のオペレーションに同じコールバック関数を指定する
遅延オブジェクトの 2 番目の利点は、複数の操作に同じコールバック関数を指定できることですが、これも従来の ajax では実現できませんでした。
$.when($.ajax({}), $.ajax({})) .done(function(){ alert("success!"); }) .fail(function(){ alert("error!"); });
2.3. 非 Ajax 操作のコールバック関数
遅延オブジェクトの 3 番目の利点は、ajax 操作に限定されなくなったことです。あらゆる操作 (ajax 操作またはローカル操作/非同期操作または同期操作) で遅延オブジェクトを使用し、コールバック関数を指定できることです。
非常に典型的な時間のかかる操作
var dfd = $.Deferred(); // create a deferred object var wait = function(dtd){ var tasks = function(){ alert("over!"); dtd.resolve(); // change the state of the deferred object from pending to resolved }; setTimeout(tasks,50000); return dtd; };
$.when(wait(dtd)) .done(function(){ alert("success!"); }) .fail(function(){ alert("error!"); });
2.4. チェーンコール
jQuery での従来の ajax 操作は次のようになります:
$.ajax({ url: "", success: function(){ alert("success!"); }, error:function(){ alert("error!"); } });
Success は、ajax 操作が成功した後のコールバック関数を指定し、error は、ajax 操作が失敗した後のコールバック関数を指定します。 jQuery 1.5 より前では、Ajax 操作は XMLHTTPRequest オブジェクトを返し、チェーン操作をサポートしていませんでした。バージョン 1.5 以降、ajax 操作は遅延オブジェクトである jqXHR オブジェクトを返します。遅延オブジェクトの大きな利点は、遅延オブジェクトのすべてのメソッドが遅延オブジェクトを返すため、連鎖操作を実行できることです。
現在の ajax 操作は次のように記述されます:
$.ajax({}) .done(function(){ alert("success!"); }) .fail(function(){ alert("fail!"); });
2 つの記述メソッドを比較すると、done() は従来の ajax 操作の success メソッドに相当し、fail() は従来の ajax 操作の failed メソッドに相当することが明確にわかります。従来の記述方法と比較して、コードの可読性が向上します。
3.遅延オブジェクトメソッド
3.1 基本的な使い方
(1).遅延オブジェクトを生成
var dfd = $.Deferred() //遅延オブジェクトを作成します
(2).遅延オブジェクトの状態
遅延オブジェクトには 3 つの状態があります
pending: は、操作が未完了の状態にあり、遅延 (遅延) オブジェクトが保留状態で開始されることを示します。
解決済み: は、操作が成功したことを示します。
rejected: は操作が失敗したことを示します。
state() メソッドは、遅延オブジェクトの現在の状態を返します。
$.Deferred().state(); // 'pending' $.Deferred().resolve().state(); // 'resolved' $.Deferred().reject().state(); // 'rejected'
(3).遅延オブジェクトの状態を変更する
deferred.resolve() または deferred.resolveWith() を呼び出して Deferred を解決済み状態に変換し、設定内の DoneCallbacks をすぐに実行します。
var callbackFunc = function(){console.log(arguments[0]);} var dfd = $.Deferred(); dfd.done(callbackFunc); dfd.resolve("hello"); //'hello'
deferred.reject() または deferred.rejectWith() を呼び出して、Deferred を拒否ステータスに変換し、設定内のfailCallbacks をすぐに実行します。
var callbackFunc = function(){console.log(arguments[0]);} var dfd = $.Deferred(); dfd.fail(callbackFunc); dfd.reject("fail"); //'fail'
(4).バインドコールバック関数
遅延オブジェクトの状態が変化すると、コールバック関数がトリガーされます。 deferred.then()、deferred.always()、deferred.done()、または deferred.fail() を使用してこのオブジェクトに追加されたコールバックは、実行のためにキューに入れられます。
pending-->resolved の場合は、設定内の noneCallbacks (done() で指定) を実行すると、resolved によってパラメータが DoneCallbacks に渡されます。
保留中-->拒否された場合は、設定内の任意のfailCallbacks(fail()で指定)を実行し、パラメータはresolvedによってfailCallbacksに渡されます。
pending-->resolved/rejected は、always() で指定されたコールバックを実行し、パラメータは解決済みからコールバックに渡されます。
var f1 = function(){console.log("done");}, f2 = function(){console.log("fail");}, f3 = function(){console.log("always");}; var dfd = $.Deferred(); dfd.done(f1).fail(f2).always(f3); //if dfd.resolve(); //'done' 'always' //if dfd.reject(); //'fail' 'always'
如果在状态更改后附件一个callback则会立即执行callback,因此不必担心deferred对象何时被resolved或者rejected,因为无论何时,参数都会正确地传递给callbacks。
var fun1 = function(){console.log(arguments[0]);}, fun1 = function(){console.log(arguments[0]);}; var dfd = $.Deferred(); dfd.done(fun1); dfd.resolve("hello"); //'hello' dfd.done(fun2); //'hello'
3.2.deferred对象的方法
(1)$.Deferred([beforeStart]) -- 创建一个deferred对象,参数类型为Function,是一个在构造函数之前调用的函数。
var func = function(){console.log("start");} var dfd = $.Deferred(func); //'start' create a deferred object
(2)deferred.done(doneCallbacks [,doneCallbacks]) -- 当deferred(延迟)对象解决时,调用添加处理程序。
args:接受一个或者多个参数,所有的参数都可以是一个单一的函数或者函数数组,当deferred(延迟)对象解决时,doneCallbacks被调用。回调是依照他们添加的顺序执行的。
var func1 = function(){console.log("1");}, func2 = function(){console.log("2");}, func3 = function(){console.log("3");}; var dfd = $.Deferred(); dfd.done([func1,func2],func3,[func2,func1]); dfd.resolve(); // "1 2 3 2 1"
(3)deferred.fail(failCallbacks [,failCallbacks]) -- 当deferred(延迟)对象拒绝时,调用添加处理程序。
args:接受一个或者多个参数,所有的参数都可以是一个单一的函数或者函数数组,当deferred(延迟)对象拒绝时,failCallbacks被调用。回调是依照他们添加的顺序执行的。
var func1 = function(){console.log("1");}, func2 = function(){console.log("2");}, func3 = function(){console.log("3");}; var dfd = $.Deferred(); dfd.fail([func1,func2],func3,[func2,func1]); dfd.reject(); // "1 2 3 2 1"
(4)deferred.resolve(args) and deferred.resolveWith(context [,args]) -- 解决Deferred(延迟)对象,并根据给定的args参数(resolveWith给定context)调用任何doneCallbacks。
参数:args -- type(object),传递给回调函数(doneCallbacks)的可选的参数,
context -- type(object),Context(上下文)作为this对象传递给完成回调函数(doneCallbacks)。
var func = function(arg){console.log(arg);}; $.Deferred().done(func).resolve("done!"); //'done!' var func = function(arg1,arg2){console.log(arg1.name + ',' + arg2);}; $.Deferred().done(func).resolve({name:'Lucy'},'How are you!'); // 'Lucy,How are you!'
resolve和resolveWith的区别就等同于fire和fireWith的区别。
var func = function () { console.log(this.name + ',' + arguments[0] + ' ' + arguments[1] + ' ' + arguments[2]); }; $.Deferred().done(func).resolveWith({ name: "Lucy" }, ["How", "are", "you!"]);//'Lucy,How are you!'
(5)deferred.reject(args) and deferred.rejectWith(context [,args]) -- 拒绝Deferred(延迟)对象,并根据给定的args参数(rejectWith给定context)调用任何failCallbacks。
参数:args -- type(object),传递给回调函数(doneCallbacks)的可选的参数,
context -- type(object),Context(上下文)作为this对象传递给完成回调函数(doneCallbacks)。
var func = function(arg){console.log(arg);}; $.Deferred().fail(func).reject("error!"); //'error!' var func = function(ctx,arg){console.log(ctx.name + ',' + arg);}; $.Deferred().fail(func).reject({name:'Mark'},'What happend!'); // 'Mark,What happend!'
reject和rejectWith的区别就等同于fire和fireWith的区别。
var func = function () { console.log(this.name + ',' + arguments[0] + ' ' + arguments[1]); }; $.Deferred().fail(func).rejectWith({ name: "Mark" }, ["what", "happend!"]); // 'Mark,What happend!'
(6)deferred.promise([target]) -- 返回Deferred(延迟)的Promise(承诺)对象。
参数可选,无参数时返回一个Promise(承诺)对象,Promise(承诺)对象仅会暴露那些需要绑定额外的处理或判断状态的延迟方法(then, done, fail, always,pipe, progress, state,和 promise)时,并不会暴露任何用于改变状态的延迟方法(resolve, reject, notify,resolveWith, rejectWith, 和 notifyWith)。使用Promise(承诺)会阻止其他人破坏你制造的promise。
function asyncEvent() { var dfd = jQuery.Deferred(); // Resolve after a random interval setTimeout(function () { dfd.resolve("hurray"); }, Math.floor(400 + Math.random() * 2000)); // Reject after a random interval setTimeout(function () { dfd.reject("sorry"); }, Math.floor(400 + Math.random() * 2000)); // Show a "working..." message every half-second setTimeout(function working() { if (dfd.state() === "pending") { dfd.notify("working... "); setTimeout(working, 500); } }, 1); // Return the Promise so caller can't change the Deferred return dfd.promise(); } // Attach a done, fail, and progress handler for the asyncEvent $.when(asyncEvent()).then( function (status) { alert(status + ", things are going well"); }, function (status) { alert(status + ", you fail this time"); }, function (status) { alert(status); } );
有参数时,会将事件绑定到参数上,然后返回该参数对象(返回的实际是一个扩展的Promise(承诺)对象)。
var obj = { hello: function (name) { alert("Hello " + name); } }, // Create a Deferred dfd = $.Deferred(); // Set object as a promise dfd.promise(obj); // Resolve the deferred dfd.resolve("John"); // Use the object as a Promise obj.done(function (name) { obj.hello(name); // will alert "Hello John" }).hello("Karl");
(7)$.when(deferreds) -- 提供一种方法来执行一个或多个对象的回调函数。
参数:type(Deferred),一个或多个延迟对象,或者普通的JavaScript对象。
参数仅传入一个单独的Deferred对象,返回它的Promise对象。
function func() { var dfd = $.Deferred(); setTimeout(function () { dfd.resolve("hurry"); }, 500); return dfd.promise(); }; $.when(func()).done(function (arg) { alert(arg); /*alert "hurry"*/ });
参数传入一个非Deferred和Promise对象,那么该参数会被当成一个被解决(resolved)的延迟对象,并且绑定到上面的任何doneCallbacks都会被立即执行。
$.when( { name: 123 } ).done( function(arg) { alert(arg.name); } /* alerts "123" */ );
无参数,返回一个resolved(解决)状态的Promise对象。
$.when().state(); // "resolved"
参数为多个Deferred对象,该方法根据一个新的“宿主” Deferred(延迟)对象,跟踪所有已通过Deferreds聚集状态,返回一个Promise对象。当所有的延迟对象被解决(resolve)时,“宿主” Deferred(延迟)对象才会解决(resolved)该方法,或者当其中有一个延迟对象被拒绝(rejected)时,“宿主” Deferred(延迟)对象就会reject(拒绝)该方法。
var d1 = $.Deferred(); var d2 = $.Deferred(); $.when( d1, d2 ).done(function ( v1, v2 ) { console.log( v1 ); // "Fish" console.log( v2 ); // "Pizza" }); d1.resolve( "Fish" ); d2.resolve( "Pizza" );
(8)deferred.then(doneFilter [,failFilter] [,progressFilter]) -- 当Deferred(延迟)对象解决,拒绝或仍在进行中时,调用添加处理程序。
参数:
doneFilter -- type(Function),当Deferred(延迟)对象得到解决时被调用的一个函数。
failFilter -- type(Function),当Deferred(延迟)对象拒绝时被调用的一个函数,可选。
progressFilter -- type(Function),当Deferred(延迟)对象生成进度通知时被调用的一个函数,可选。
其实,then方法可以理解成,把done(),fail(),progress()合在一起写。
var filterResolve = function () { var dfd = $.Deferred(), filtered = dfd.then(function (value) { return value * 2; }); dfd.resolve(5); filtered.done(function (value) { console.log(value); }); }; filterResolve(); //'10' var defer = $.Deferred(), filtered = defer.then(null, function (value) { return value * 3; }); defer.reject(6); filtered.fail(function (value) { alert("Value is 3*6 = " + value); });
(9)deferred.always(alwaysCallbacks [,alwaysCallbacks]) -- 当Deferred(延迟)对象解决或拒绝时,执行alwaysCallbacks。
顾名思义,只要Deferred对象的状态发生更改(解决或者拒绝)均会调用alwaysCallbacks。
(10)deferred.state() -- 获取一个Deferred(延迟)对象的当前状态,不接受任何参数。
$.Deferred().state();//"pending"
上面讲述过deferre(延迟)对象的三种状态,这个方法对于debug非常有用,例如,在准备reject一个deferred对象之前,判断它是否处于resolved状态。
(11)deferred.notify(args) and deferred.notifyWith()
(12)deferred.progress()
(13)deferred.pipe()
(14).promise()
(15)deferred.isRejected() 和 deferred.isResolved() -- 从jQuery 1.7开始被弃用,较新版本的jQuery类库中已经被删除,可以使用state()方法代替这两个方法。
(16)deferred.pipe() -- 从jQuery 1.8开始被弃用。
4.什么情况下使用deferred对象和Promises?
上面讲了很多,那么我们究竟在什么情况下使用Deferred对象和Promises对象呢?
(1)复杂的动画
不知道动画什么时候结束,但是又必须在动画结束的时候做一些操作或者是启动其他的动画,这种情况下,如果采用其他的方式,很容易导致代码可读性差,尤其是还夹带着一些其它的操作,比如渲染、表单操作等,现在jQuery会为你的动画操作返回一个Promise,这样这些动画可以进行链式操作。
(2)处理队列
function wait(ms) { var deferred = $.Deferred(); setTimeout(function(){deferred.resolve()}, ms); return deferred.promise(); } wait(1500).then(function () { // After 1500ms this will be executed });
(4)典型的Ajax操作
$.when($.ajax({}), $.ajax({})) .done(function(){ alert("success!"); }) .fail(function(){ alert("error!"); });
(5)一些耗时的大循环操作
以上就是本文的全部内容,希望对大家的学习有所帮助。