Jak można odłożyć jQuery?


279

jQuery 1,5 przynosi nowe odroczony przedmiot i dołączone metody .when, .Deferredi ._Deferred.

Dla tych, którzy nie używali .Deferredwcześniej, mam uwagami na źródło dla niego .

Jakie są możliwe zastosowania tych nowych metod, w jaki sposób zajmiemy się dopasowaniem ich do wzorców?

Przeczytałem już interfejs API i źródło , więc wiem, co robi. Moje pytanie brzmi: w jaki sposób możemy korzystać z tych nowych funkcji w codziennym kodzie?

Mam prosty przykład klasy bufora, która po kolei wywołuje żądanie AJAX. (Następny zaczyna się po zakończeniu poprzedniego).

/* 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();
    };
};

Szukam demonstracji i możliwych zastosowań .Deferredi .when.

Byłoby również miło zobaczyć przykłady ._Deferred.

Powiązanie z nowym jQuery.ajaxźródłem przykładów to oszustwo.

Szczególnie interesuje mnie, jakie techniki są dostępne, gdy wyodrębniamy, czy operacja jest wykonywana synchronicznie, czy asynchronicznie.


19
Z FAQ: unikaj zadawania subiektywnych pytań, gdzie ... każda odpowiedź jest równie ważna: „Jaki jest twój ulubiony ______?” (ich podkreślenie)
— TJ Crowder

2
@TJCrowser Popatrzę na przeredagowanie go.
— Raynos

5
To dobre pytanie, ale nie może być tak wielu ludzi, którzy mogą odpowiedzieć :-)
— Pointy

2
@Pointy Patrzę głównie na tych, którzy korzystali z niej, gdy była to wtyczka innej firmy. I zachęcanie ludzi do siedzenia i korzystania z niego!
— Raynos

1
._Deferredjest po prostu prawdziwym „Odroczonym obiektem”, który .Deferredwykorzystuje. To wewnętrzny obiekt, którego najprawdopodobniej nigdy nie będziesz potrzebować.
— David Tang

Odpowiedzi:


212

Najlepszy przypadek użycia, jaki mogę wymyślić, to buforowanie odpowiedzi AJAX. Oto zmodyfikowany przykład z postu wprowadzającego Rebecca Murphey na ten temat :

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.
});

Zasadniczo, jeśli wartość została już raz zażądana, zanim zostanie natychmiast zwrócona z pamięci podręcznej. W przeciwnym razie żądanie AJAX pobiera dane i dodaje je do pamięci podręcznej. $.when/ .thenNie dba o nic z tego; wszystko, o co musisz się martwić, to skorzystanie z odpowiedzi, która jest przekazywana do programu .then()obsługi w obu przypadkach. jQuery.when()obsługuje niezrealizowaną / odroczoną jako zakończoną, natychmiast wykonując dowolną .done()lub .then()w łańcuchu.

Odroczenia są idealne na wypadek, gdy zadanie może działać asynchronicznie lub nie, a chcesz wyodrębnić ten warunek z kodu.

Kolejny przykład z użyciem $.whenpomocnika:

$.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
Dwa przykłady brylantów. Zaimplementowałem coś podobnego do drugiego, ale z 4 żądaniami ajax i działa dobrze, oprócz tego, że jest o wiele bardziej czytelny, zwarty, logiczny, łatwy w utrzymaniu itp. JQuery.Deferred to naprawdę dobra rzecz.
— PJP


5
Buforowanie nie będzie działać, jeśli wynikiem będzie wartość fałsz. Nie podoba mi się również fakt, że getData zwraca 2 różne typy w zależności od branej gałęzi.
— Marko Dumic

3
Zobacz odpowiedź Juliana D. poniżej, aby uzyskać lepszą implementację buforowania ajax.
— event_jr

1
Nie rozumiem nawet, jak działa pierwszy przykład kodu: rozumiem przypadek, w którym obiekt nie jest buforowany, ale jeśli tak, to cache[ val ]NIE zwróci obietnicy (dokumentacja jquery mówi, że parametr to dane zwrócone przez nadawcę), co oznacza, że dostęp członka .thenbędzie błąd ... prawda? czego mi brakuje?
— chacham15

79

Oto nieco inna implementacja pamięci podręcznej AJAX niż w odpowiedzi ehynd .

Jak zauważono w pytaniu uzupełniającym fortuneRice, implementacja ehynd nie zapobiegła wielu identycznym żądaniom, jeśli żądania zostały wykonane przed zwrotem jednego z nich. To jest,

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

najprawdopodobniej spowoduje 3 żądania AJAX, jeśli wynik dla „xxx” nie był wcześniej buforowany.

Można to rozwiązać, buforując odroczone żądanie zamiast wyniku:

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
Myślę, że nadal nie jest to idealne, ponieważ nigdy nie wyczyścisz / nie zaktualizujesz pamięci podręcznej po pierwszym pobraniu. Spowoduje to, że wywołanie AJAX nie będzie działać dla żadnej aktualizacji.
— zyzyis

45

Odroczenie może być użyte zamiast muteksu. Jest to w zasadzie to samo co scenariusze użycia wielu aukcji.

MUTEX

var mutex = 2;

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

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

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

ODROCZONY

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
});

Używając opcji Odroczony jako muteksu, uważaj na wpływ na wydajność (http://jsperf.com/deferred-vs-mutex/2). Chociaż wygoda, a także dodatkowe korzyści zapewniane przez Odroczone są tego warte, a faktyczne (oparte na zdarzeniach użytkownika) wykorzystanie wydajności nie powinno być zauważalne.


Zaskakująco trudno było mi to znaleźć. Użyłem go w funkcji zawierającej setInterval, która zwróciłaby rozwiązaną obietnicę i uległa samozniszczeniu, gdy szerokość div przekroczyła określoną liczbę. To było na rozwiązywanie problemów i rozwiązanie, jeśli nie mogłem rozwiązać problemu, ale jestem zachwycony.
— JSG


20

Kolejnym zastosowaniem, który wykorzystałem w dobrym celu, jest pobieranie danych z wielu źródeł. W poniższym przykładzie pobieram wiele niezależnych obiektów schematu JSON używanych w istniejącej aplikacji do sprawdzania poprawności między klientem a serwerem REST. W tym przypadku nie chcę, aby aplikacja po stronie przeglądarki rozpoczynała ładowanie danych, zanim załadowane zostaną wszystkie schematy. $ .when.apply (). then () jest do tego idealny. Dziękujemy Raynosowi za wskazówki dotyczące używania wtedy (fn1, fn2) do monitorowania warunków błędów.

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

Kolejny przykład użycia Deferreds do zaimplementowania pamięci podręcznej dla dowolnego rodzaju obliczeń (zwykle niektóre zadania wymagające dużej wydajności lub długotrwałe):

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("|");
};

Oto przykład użycia tej klasy do wykonania niektórych (symulowanych ciężkich) obliczeń:

// 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);
});

Tej samej pamięci podręcznej można użyć do buforowania żądań Ajax:

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);
});

Możesz grać z powyższym kodem w tym jsFiddle .


9

1) Użyj go, aby zapewnić uporządkowane wykonanie oddzwaniania:

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) Użyj go, aby zweryfikować status aplikacji:

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

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

2

Możesz użyć odroczonego obiektu, aby stworzyć płynny projekt, który działa dobrze w przeglądarkach webkit. Przeglądarki Webkit będą uruchamiać zdarzenie zmiany rozmiaru dla każdego piksela, którego rozmiar jest zmieniany, w przeciwieństwie do FF i IE, które uruchamiają zdarzenie tylko raz dla każdej zmiany rozmiaru. W rezultacie nie masz kontroli nad kolejnością wykonywania funkcji związanych ze zdarzeniem zmiany rozmiaru okna. Coś takiego rozwiązuje problem:

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);
});

Spowoduje to serializację wykonania kodu, tak aby działał zgodnie z przeznaczeniem. Uważaj na pułapki przy przekazywaniu metod obiektowych jako oddzwaniania do odroczonego. Gdy taka metoda zostanie wykonana jako wywołanie zwrotne do odroczenia, odniesienie „to” zostanie zastąpione odniesieniem do odroczonego obiektu i nie będzie już odnosiło się do obiektu, do którego należy metoda.


Jak to robi jakąkolwiek serializację? Już rozwiązałeś kolejkę, więc resizeQueue.done(resizeAlgorithm)jest dokładnie tak samo jak resizeAlgorithm. To kompletne oszustwo!
— Raynos

Gdy kod Twojego algorytmu resizeAlgorytm jest złożony, implementacja JavaScript w pakiecie internetowym utraci synchronizację, gdy funkcja zostanie wywołana dla każdego piksela, którego rozmiar zmienia się w oknie. Odroczony utrzymuje oddzwanianie w kolejce i wykonuje je w kolejności FIFO. Jeśli więc dodasz wywołanie zwrotne „gotowe” i zostanie ono wykonane natychmiast, ponieważ odroczone jest już rozwiązane, kolejne wywołanie zwrotne „gotowe”, które zostanie dodane do odroczonego podczas pierwszego wykonywania, zostanie dodane do kolejki i będzie musiało poczekać do pierwszego połączenia zwrotnego. Mam nadzieję, że to odpowiada na twoje pytanie.
— Miloš Rašić

interpreter JS w przeglądarce jest jednowątkowy. O ile twój resizeAlgorytm nie zawiera kodu asynchronicznego, cała funkcja powinna zakończyć działanie przed wykonaniem następnego wywołania .done.
— Raynos

@Raynos: Zdaję sobie z tego sprawę, ale próbowałem po prostu wywołać algorytm resizeAlgorytm przy zmianie rozmiaru i daje pustą białą stronę w przeglądarkach webkit, podczas gdy działa doskonale w innych. Odroczony rozwiązuje ten problem. Nie miałem czasu na głębsze badania w tym zakresie. Może to być błąd webkita. Nie sądzę, aby odroczenie zastosowane w moim przykładzie pomogło, gdyby resizeAlgorithm zawierał kod asynchroniczny.
— Miloš Rašić

2
Nie powinieneś używać czegoś takiego jak wtyczka przepustnicy / debounce benalman.com/projects/jquery-throttle-debounce-plugin, aby zapobiec uruchamianiu większej liczby funkcji raz na zmianę rozmiaru.
— wheresrhys

2

Możesz także zintegrować go z dowolnymi bibliotekami innych firm korzystającymi z JQuery.

Jedną z takich bibliotek jest Backbone, która w rzeczywistości będzie obsługiwać Odroczoną w ich następnej wersji.


2
Użyj read more herezamiast on my blog. Jest to lepsza praktyka i może uchronić Cię przed (przypadkowym) spamowaniem. :)
— Lokesh Mehra

1

Właśnie użyłem opcji Odroczony w prawdziwym kodzie. W projekcie jQuery Terminal mam funkcję exec, która wywołuje polecenia zdefiniowane przez użytkownika (tak jak on wchodził do niej i naciskał enter), dodałem Deferreds do API i wywołuje exec z tablicami. lubię to:

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

lub

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

polecenia mogą uruchamiać kod asynchroniczny, a exec musi wywoływać kod użytkownika w kolejności. Mój pierwszy interfejs API wykorzystuje parę połączeń pauzujących / wznawiających, aw nowym interfejsie API wywołuję te automatycznie, gdy użytkownik zwróci obietnicę. Więc kod użytkownika może po prostu użyć

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

lub

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

Używam takiego kodu:

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 jest używany w funkcji wznawiania, która ponownie wywołuje exec ze wszystkimi dalyed_commands.

i część funkcji poleceń (usunąłem niepowiązane części)

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

Odpowiedź ehynds nie będzie działać, ponieważ buforuje dane odpowiedzi. Powinien buforować jqXHR, który jest również obietnicą. Oto poprawny 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.
});

Odpowiedź Juliana D. będzie działać poprawnie i jest lepszym rozwiązaniem.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.