Ertelenmiş jQuery nasıl kullanılabilir?


279

jQuery 1.5 yeni Ertelenmiş nesne ve ekli yöntemleri getirir .when, .Deferredve ._Deferred.

Daha .Deferredönce kullanmayanlar için bunun kaynağına açıklama ekledim .

Bu yeni yöntemlerin olası kullanımları nelerdir, bunları kalıplara uydurmaya nasıl gidebiliriz?

API ve kaynağı zaten okudum , bu yüzden ne yaptığını biliyorum. Sorum şu: Bu yeni özellikleri günlük kodda nasıl kullanabiliriz?

AJAX isteği sırayla çağıran bir arabellek sınıfı basit bir örneği var . (Bir öncekinin bitmesinden sonra bir sonraki başlangıç).

/* Class: Buffer
 *  methods: append
 *
 *  Constructor: takes a function which will be the task handler to be called
 *
 *  .append appends a task to the buffer. Buffer will only call a task when the 
 *  previous task has finished
 */
var Buffer = function(handler) {
    var tasks = [];
    // empty resolved deferred object
    var deferred = $.when();

    // handle the next object
    function handleNextTask() {
        // if the current deferred task has resolved and there are more tasks
        if (deferred.isResolved() && tasks.length > 0) {
            // grab a task
            var task = tasks.shift();
            // set the deferred to be deferred returned from the handler
            deferred = handler(task);
            // if its not a deferred object then set it to be an empty deferred object
            if (!(deferred && deferred.promise)) {
                deferred = $.when();
            }
            // if we have tasks left then handle the next one when the current one 
            // is done.
            if (tasks.length > 0) {
                deferred.done(handleNextTask);
            }
        }
    }

    // appends a task.
    this.append = function(task) {
        // add to the array
        tasks.push(task);
        // handle the next task
        handleNextTask();
    };
};

Ben gösteriler ve olası kullanımlar için arıyorum .Deferredve .when.

Örneklerini görmek de güzel olurdu ._Deferred.

jQuery.ajaxÖrnekler için yeni kaynağa bağlanmak hile yapmaktır.

Bir işlemin eşzamanlı veya eşzamansız olarak yapılıp yapılmadığını özetlediğimizde özellikle hangi tekniklerin mevcut olduğu ile ilgileniyorum.


19
SSS'den: Nerede ... her cevabın eşit derecede geçerli olduğu öznel sorular sormaktan kaçının : “En sevdiğiniz ______ nedir?” (vurgu)
TJ Crowder

2
@TJCrowser Yeniden düzenlemeye bakacağım.
Raynos

5
Güzel bir soru ama cevap verebilecek pek çok insan olamaz :-)
Pointy

2
@Pointy Esas olarak 3. taraf bir eklenti olduğunda kullananlara bakıyorum. Ve insanları oturmaya ve kullanmaya teşvik etmek!
Raynos

1
._Deferredbasitçe kullanılan "Ertelenmiş nesne" dir .Deferred. Bu, muhtemelen hiç ihtiyacınız olmayacak bir iç nesnedir.
David Tang

Yanıtlar:


212

Aklıma gelen en iyi kullanım durumu AJAX yanıtlarını önbelleğe almak. İşte Rebecca Murphey'in konuyla ilgili giriş yayınından değiştirilmiş bir örnek :

var cache = {};

function getData( val ){

    // return either the cached value or jqXHR object wrapped Promise
    return $.when(
        cache[ val ] || 
        $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json',
            success: function( resp ){
                cache[ val ] = resp;
            }
        })
    );
}

getData('foo').then(function(resp){
    // do something with the response, which may
    // or may not have been retrieved using an
    // XHR request.
});

Temel olarak, değer önbellekten hemen döndürülmeden önce bir kez istenmişse. Aksi takdirde, bir AJAX isteği verileri alır ve önbelleğe ekler. $.when/ Bunların .thenhiçbirini umursamıyor; endişelenmeniz gereken tek şey, .then()her iki durumda da işleyiciye iletilen yanıtı kullanmaktır . jQuery.when()söz verilen veya Ertelenmiş olarak bir Vaat Olmayan / Ertelenmiş, hemen zincir üzerinde .done()veya .then()zincir üzerinde yürütür .

Ertelemeler, görevin eşzamansız olarak çalışabileceği veya çalışmayabileceği zamanlar için mükemmeldir ve bu koşulu koddan çıkarmak istediğinizde mükemmeldir.

$.whenYardımcıyı kullanan başka bir gerçek dünya örneği :

$.when($.getJSON('/some/data/'), $.get('template.tpl')).then(function (data, tmpl) {

    $(tmpl) // create a jQuery object out of the template
    .tmpl(data) // compile it
    .appendTo("#target"); // insert it into the DOM

});

4
İki pırlanta örneği. Ben ikincisine benzer bir şey uyguladım, ancak 4 ajax isteği ile ve çok daha okunaklı, kompakt, mantık, bakımı yapılabilir, vb.İçin iyi bir performans sergiliyor.
PJP

5
İşte bu konuda yararlı bir video bigbinary.com/videos/3-using-deferred-in-jquery
Nick Vanderbilt

5
Sonuç yanlış değerse önbellekleme çalışmaz. Ayrıca getData alınan şubeye bağlı olarak 2 farklı tür döndürür sevmiyorum.
Marko Dumic

3
Ajax önbelleğe almanın daha iyi uygulanması için Julian D.'nin cevabına bakınız.
event_jr

1
İlk kod örneğinin nasıl çalıştığını anlamıyorum: Nesnenin önbelleğe alınmadığı durumu anlıyorum, ancak daha sonra cache[ val ]bir söz vermeyecekse (jquery belgeleri parametrenin gönderen tarafından döndürülen veriler olduğunu söylüyor) üye erişim .thenhatası olacak ... değil mi? Neyi kaçırıyorum?
chacham15

79

İşte ehynd cevabında olduğu gibi bir AJAX önbelleğinin biraz farklı bir uygulaması .

FortuneRice'in takip sorusunda belirtildiği gibi , istekler biri geri dönmeden önce gerçekleştirildiyse, ehynd'ın uygulanması aslında birden fazla özdeş isteği engellemedi. Yani,

for (var i=0; i<3; i++) {
    getData("xxx");
}

"xxx" sonucu önceden önbelleğe alınmamışsa büyük olasılıkla 3 AJAX isteğiyle sonuçlanır.

Bu, sonucun yerine isteğin Ertelenmelerini önbelleğe alarak çözülebilir:

var cache = {};

function getData( val ){

    // Return a promise from the cache (if available)
    // or create a new one (a jqXHR object) and store it in the cache.
    var promise = cache[val];
    if (!promise) {
        promise = $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json'
        });
        cache[val] = promise;
    }
    return promise;
}

$.when(getData('foo')).then(function(resp){
    // do something with the response, which may
    // or may not have been retreived using an
    // XHR request.
});

1
İlk alındığında önbelleği asla temizlemediğiniz / güncellemediğiniz için bunun hala mükemmel olmadığını düşünüyorum. Bu, AJAX çağrısının herhangi bir güncelleme için çalışmamasını sağlar.
zyzyis

45

Ertelenmiş bir muteks yerine kullanılabilir. Bu aslında çoklu ajax kullanım senaryolarıyla aynıdır.

Karşılıklı dışlama

var mutex = 2;

setTimeout(function() {
 callback();
}, 800);

setTimeout(function() {
 callback();
}, 500);

function callback() {
 if (--mutex === 0) {
  //run code
 }
}

ERTELENMİŞ

function timeout(x) {
 var dfd = jQuery.Deferred();
 setTimeout(function() {
  dfd.resolve();
 }, x);
 return dfd.promise();
}

jQuery.when(
timeout(800), timeout(500)).done(function() {
 // run code
});

Bir Ertelenmiş'i yalnızca muteks olarak kullanırken, performans etkilerine dikkat edin (http://jsperf.com/deferred-vs-mutex/2). Her ne kadar kolaylık ve bir Ertelenmiş tarafından sağlanan ek faydalar buna değer olsa da ve gerçek (kullanıcı odaklı olay tabanlı) kullanımda performans etkisi fark edilmemelidir.


Bunu bulmak benim için şaşırtıcı derecede zordu. Ben bir setInterval içeren bir işlev üzerinde çözülmüş vaat döndürecek ve div genişliği belirli bir sayı üzerinde yapılan kez kendini imha etti. Sorunumu çözemezsem sorun giderme ve çözüm içindi, ama bu konuda kendinden geçmişim.
JSG


20

İyi bir amaç için kullandığım bir başka kullanım da birden çok kaynaktan veri almak. Aşağıdaki örnekte, bir istemci ve bir REST sunucusu arasında doğrulama için var olan bir uygulamada kullanılan birden çok bağımsız JSON şema nesnesi getiriyorum. Bu durumda, tarayıcı tarafı uygulamasının tüm şemalar yüklenmeden önce veri yüklemeye başlamasını istemiyorum. $ .when.apply (). sonra () bunun için mükemmeldir. Hata koşullarını izlemek için o zaman (fn1, fn2) kullanılmasına ilişkin işaretçiler için Raynos'a teşekkür ederiz.

fetch_sources = function (schema_urls) {
    var fetch_one = function (url) {
            return $.ajax({
                url: url,
                data: {},
                contentType: "application/json; charset=utf-8",
                dataType: "json",
            });
        }
    return $.map(schema_urls, fetch_one);
}

var promises = fetch_sources(data['schemas']);
$.when.apply(null, promises).then(

function () {
    var schemas = $.map(arguments, function (a) {
        return a[0]
    });
    start_application(schemas);
}, function () {
    console.log("FAIL", this, arguments);
});     

10

DeferredHerhangi bir hesaplama için önbellek uygulamak için s'yi kullanan başka bir örnek (genellikle bazı performans yoğun veya uzun süren görevler):

var ResultsCache = function(computationFunction, cacheKeyGenerator) {
    this._cache = {};
    this._computationFunction = computationFunction;
    if (cacheKeyGenerator)
        this._cacheKeyGenerator = cacheKeyGenerator;
};

ResultsCache.prototype.compute = function() {
    // try to retrieve computation from cache
    var cacheKey = this._cacheKeyGenerator.apply(this, arguments);
    var promise = this._cache[cacheKey];

    // if not yet cached: start computation and store promise in cache 
    if (!promise) {
        var deferred = $.Deferred();
        promise = deferred.promise();
        this._cache[cacheKey] = promise;

        // perform the computation
        var args = Array.prototype.slice.call(arguments);
        args.push(deferred.resolve);
        this._computationFunction.apply(null, args);
    }

    return promise;
};

// Default cache key generator (works with Booleans, Strings, Numbers and Dates)
// You will need to create your own key generator if you work with Arrays etc.
ResultsCache.prototype._cacheKeyGenerator = function(args) {
    return Array.prototype.slice.call(arguments).join("|");
};

Aşağıda, bazı (simüle edilmiş ağır) hesaplamalar yapmak için bu sınıfı kullanma örneği:

// The addingMachine will add two numbers
var addingMachine = new ResultsCache(function(a, b, resultHandler) {
    console.log("Performing computation: adding " + a + " and " + b);
    // simulate rather long calculation time by using a 1s timeout
    setTimeout(function() {
        var result = a + b;
        resultHandler(result);
    }, 1000);
});

addingMachine.compute(2, 4).then(function(result) {
    console.log("result: " + result);
});

addingMachine.compute(1, 1).then(function(result) {
    console.log("result: " + result);
});

// cached result will be used
addingMachine.compute(2, 4).then(function(result) {
    console.log("result: " + result);
});

Aynı temel önbellek Ajax isteklerini önbelleğe almak için kullanılabilir:

var ajaxCache = new ResultsCache(function(id, resultHandler) {
    console.log("Performing Ajax request for id '" + id + "'");
    $.getJSON('http://jsfiddle.net/echo/jsonp/?callback=?', {value: id}, function(data) {
        resultHandler(data.value);
    });
});

ajaxCache.compute("anID").then(function(result) {
    console.log("result: " + result);
});

ajaxCache.compute("anotherID").then(function(result) {
    console.log("result: " + result);
});

// cached result will be used
ajaxCache.compute("anID").then(function(result) {
    console.log("result: " + result);
});

Bu jsFiddle yukarıdaki kod ile oynayabilirsiniz .


9

1) Geri aramaların düzenli bir şekilde yürütülmesini sağlamak için kullanın:

var step1 = new Deferred();
var step2 = new Deferred().done(function() { return step1 });
var step3 = new Deferred().done(function() { return step2 });

step1.done(function() { alert("Step 1") });
step2.done(function() { alert("Step 2") });
step3.done(function() { alert("All done") });
//now the 3 alerts will also be fired in order of 1,2,3
//no matter which Deferred gets resolved first.

step2.resolve();
step3.resolve();
step1.resolve();

2) Uygulamanın durumunu doğrulamak için kullanın:

var loggedIn = logUserInNow(); //deferred
var databaseReady = openDatabaseNow(); //deferred

jQuery.when(loggedIn, databaseReady).then(function() {
  //do something
});

2

Webkit tarayıcılarında iyi çalışan bir akışkan tasarımı yapmak için ertelenmiş bir nesne kullanabilirsiniz. Webkit tarayıcıları, olayı her yeniden boyutlandırma için yalnızca bir kez tetikleyen FF ve IE'den farklı olarak, pencerenin yeniden boyutlandırıldığı her piksel için yeniden boyutlandırma olayını tetikler. Sonuç olarak, pencere yeniden boyutlandırma etkinliğinize bağlı işlevlerin yürütülme sırası üzerinde hiçbir denetiminiz olmaz. Böyle bir şey sorunu çözer:

var resizeQueue = new $.Deferred(); //new is optional but it sure is descriptive
resizeQueue.resolve();

function resizeAlgorithm() {
//some resize code here
}

$(window).resize(function() {
    resizeQueue.done(resizeAlgorithm);
});

Bu, kodunuzun yürütülmesini istediğiniz şekilde çalıştıracak şekilde serileştirir. Ertelenenlere geri çağrı olarak nesne yöntemlerini iletirken tuzaklara dikkat edin. Böyle bir yöntem ertelenmiş bir geri çağrı olarak yürütüldüğünde, ertelenmiş nesneye referansla 'bu' referansın üzerine yazılır ve artık yöntemin ait olduğu nesneye atıfta bulunulmaz.


Bu nasıl serileştirme yapar? Kuyruğu zaten çözdünüz, bu resizeQueue.done(resizeAlgorithm)da tam olarak aynı resizeAlgorithm. Tam bir sahte!
Raynos

ResizeAlgorithm kodunuz karmaşık olduğunda, webkit'teki JavaScript uygulaması, pencereyi yeniden boyutlandırdığınız her piksel için işlev çağrıldığında senkronizasyonu kaybeder. Ertelenmiş, geri aramalarınızı bir kuyrukta tutar ve bir FIFO düzeninde yürütür. Dolayısıyla, 'tamamlanmış' bir geri arama eklerseniz ve ertelenmiş halihazırda ertelendiği için hemen yürütülürse, ilk geri arama devam ederken ertelemeye eklenen başka bir 'tamamlanmış' geri arama kuyruğa eklenir ve beklemek zorunda kalır ilk geri arama için. Umarım bu soruya cevap verir.
Miloš Rašić

tarayıcıdaki JS yorumlayıcısı tek iş parçacıklıdır. ResizeAlgorithm'inizde bazı eşzamansız kod yoksa, bir sonraki çağrı .doneyapılmadan önce tüm işlevin çalışması tamamlanmış olmalıdır .
Raynos

@Raynos: Bunun farkındayım, ancak yeniden boyutlandırma üzerinde resizeAlgorithm'i çağırmaya çalıştım ve başkalarında mükemmel çalışırken webkit tarayıcılarında boş bir beyaz sayfa veriyor. Ertelenmiş bu sorunu çözer. Bununla ilgili daha derin araştırmalar yapmak için yeterli zamanım olmadı. Bir webkit hatası olabilir. ResizeAlgorithm bazı eşzamansız kod varsa, benim örnekte kullanılan ertelenmiş yardımcı olacağını sanmıyorum.
Miloš Rašić

2
Fonksiyonlarınızın yeniden boyutlandırma başına bir kez daha fazla tahnesi tetiklemesini önlemek için gaz / debounce eklentisi benalman.com/projects/jquery-throttle-debounce-plugin gibi bir şey kullanmamalısınız .
wheresrhys

2

Ayrıca, JQuery'yi kullanan herhangi bir 3. taraf kitaplığıyla da entegre edebilirsiniz.

Bu tür bir kütüphane, aslında bir sonraki versiyonunda Deferred'ü destekleyecek olan Backbone'dur.


2
Kullanım read more hereyerine on my blog. Bu daha iyi bir uygulamadır ve (yanlışlıkla) spam gönderilmesinden yanıtınızı kurtarabilir. :)
Lokesh Mehra

1

Ben sadece gerçek kodda Ertelenmiş kullandım. Proje jQuery Terminali Ben kullanıcı tarafından tanımlanan komutları (o giriyor ve enter tuşuna basın gibi) tanımlanmış fonksiyon exec var, API ertelemeler ekledim ve diziler ile exec çağrı. bunun gibi:

terminal.exec('command').then(function() {
   terminal.echo('command finished');
});

veya

terminal.exec(['command 1', 'command 2', 'command 3']).then(function() {
   terminal.echo('all commands finished');
});

komutlar zaman uyumsuz kod çalıştırabilir ve exec sırayla kullanıcı kodunu çağırmak gerekir. İlk API'm çift duraklatma / özgeçmiş çağrısı kullanıyorum ve yeni API'de kullanıcı geri dönüş söz verdiğinde bu otomatik olarak adlandırıyorum. Böylece kullanıcı kodu sadece

return $.get('/some/url');

veya

var d = new $.Deferred();
setTimeout(function() {
    d.resolve("Hello Deferred"); // resolve value will be echoed
}, 500);
return d.promise();

Ben böyle kodu kullanın:

exec: function(command, silent, deferred) {
    var d;
    if ($.isArray(command)) {
        return $.when.apply($, $.map(command, function(command) {
            return self.exec(command, silent);
        }));
    }
    // both commands executed here (resume will call Term::exec)
    if (paused) {
        // delay command multiple time
        d = deferred || new $.Deferred();
        dalyed_commands.push([command, silent, d]);
        return d.promise();
    } else {
        // commands may return promise from user code
        // it will resolve exec promise when user promise
        // is resolved
        var ret = commands(command, silent, true, deferred);
        if (!ret) {
            if (deferred) {
                deferred.resolve(self);
                return deferred.promise();
            } else {
                d = new $.Deferred();
                ret = d.promise();
                ret.resolve();
            }
        }
        return ret;
    }
},

dalyed_commands, tüm dalyed_commands ile tekrar exec çağıran özgeçmiş işlevinde kullanılır.

ve komutlar fonksiyonunun bir parçası (İlişkili olmayan parçaları çıkardım)

function commands(command, silent, exec, deferred) {

    var position = lines.length-1;
    // Call user interpreter function
    var result = interpreter.interpreter(command, self);
    // user code can return a promise
    if (result != undefined) {
        // new API - auto pause/resume when using promises
        self.pause();
        return $.when(result).then(function(result) {
            // don't echo result if user echo something
            if (result && position === lines.length-1) {
                display_object(result);
            }
            // resolve promise from exec. This will fire
            // code if used terminal::exec('command').then
            if (deferred) {
                deferred.resolve();
            }
            self.resume();
        });
    }
    // this is old API
    // if command call pause - wait until resume
    if (paused) {
        self.bind('resume.command', function() {
            // exec with resume/pause in user code
            if (deferred) {
                deferred.resolve();
            }
            self.unbind('resume.command');
        });
    } else {
        // this should not happen
        if (deferred) {
            deferred.resolve();
        }
    }
}

1

Ehynds tarafından cevap çalışmaz, çünkü cevap verisini önbelleğe alır. Ayrıca bir Promise olan jqXHR'yi önbelleğe almalıdır. İşte doğru kod:

var cache = {};

function getData( val ){

    // return either the cached value or an
    // jqXHR object (which contains a promise)
    return cache[ val ] || $.ajax('/foo/', {
        data: { value: val },
        dataType: 'json',
        success: function(data, textStatus, jqXHR){
            cache[ val ] = jqXHR;
        }
    });
}

getData('foo').then(function(resp){
    // do something with the response, which may
    // or may not have been retreived using an
    // XHR request.
});

Julian D.'nin cevabı doğru çalışacak ve daha iyi bir çözüm olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.