Kiedy należy używać metody „then” jQuery deferred, a kiedy metody „potoku”?

97

jQuery Deferredma dwie funkcje, które można wykorzystać do implementacji asynchronicznego łączenia funkcji w łańcuchy:

then()

deferred.then( doneCallbacks, failCallbacks ) Returns: Deferred

doneCallbacks Funkcja lub tablica funkcji wywoływana po rozwiązaniu funkcji Deferred .
failCallbacks Funkcja lub tablica funkcji wywoływana w przypadku odrzucenia funkcji Deferred .

pipe()

deferred.pipe( [doneFilter] [, failFilter] ) Returns: Promise

doneFilter Opcjonalna funkcja, która jest wywoływana po rozwiązaniu Deferred .
failFilter Opcjonalna funkcja, która jest wywoływana, gdy Deferred zostanie odrzucona.

Wiem, then()że trwało to trochę dłużej, niż pipe()więc ta ostatnia musi przynosić dodatkowe korzyści, ale jaka różnica dokładnie mi umyka. Oba przyjmują prawie te same parametry wywołania zwrotnego, chociaż różnią się nazwą, a różnica między zwróceniem a Deferreda zwróceniem a Promisewydaje się niewielka.

Czytałem oficjalne dokumenty w kółko, ale zawsze uważałem je za zbyt „gęste”, by naprawdę zawinąć głowę, a wyszukiwanie znalazło wiele dyskusji na temat jednej lub drugiej funkcji, ale nie znalazłem niczego, co naprawdę wyjaśniałoby różne wady i zalety każdego z nich.

Kiedy więc lepiej używać, thena kiedy lepiej używać pipe?


Dodanie

Doskonała odpowiedź Felixa naprawdę pomogła wyjaśnić, czym różnią się te dwie funkcje. Ale zastanawiam się, czy są chwile, w których funkcjonalność then()jest lepsza niż pipe().

Widać, że pipe()jest potężniejszy niż then()i wydaje się, że ten pierwszy może zrobić wszystko, co drugi. Jednym z powodów użycia then()może być fakt, że jego nazwa odzwierciedla jego rolę jako zakończenia łańcucha funkcji przetwarzających te same dane.

Ale czy istnieje przypadek użycia, który wymaga then()zwrotu oryginału, z Deferredktórym nie można zrobić, pipe()ponieważ zwraca nowy Promise?

hippietrail
źródło
1
Myślałem o tym przez chwilę, ale nie mogę wymyślić żadnego przypadku użycia. Tworzenie nowych obiektów obietnic może być po prostu obciążeniem, jeśli ich nie potrzebujesz (nie wiem, jak są połączone wewnętrznie łańcuchem). To powiedziawszy, z pewnością są ludzie, którzy rozumieją to lepiej niż ja.
Felix Kling
6
Każdy zainteresowany w tej kwestii z pewnością zainteresujesz się numerem # 11010 na bug tracker jQuery: MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE / A
hippietrail

Odpowiedzi:

103

Ponieważ jQuery 1.8 .then zachowuje się tak samo jak .pipe:

Powiadomienie o deferred.pipe()wycofaniu : od wersji jQuery 1.8 ta metoda jest przestarzała. deferred.then()Metoda, która zastępuje go, powinien być stosowany zamiast.

i

Począwszy od jQuery 1.8 , deferred.then()metoda zwraca nową obietnicę, która może filtrować stan i wartości odroczonego za pomocą funkcji, zastępując deferred.pipe()metodę , która jest obecnie przestarzała .

Poniższe przykłady mogą być nadal pomocne dla niektórych.


Służą różnym celom:

  • .then()ma być używany zawsze, gdy chcesz pracować z wynikiem procesu, tj. jak mówi dokumentacja, gdy odroczony obiekt zostanie rozwiązany lub odrzucony. To jest to samo, co użycie .done()lub .fail().

  • W jakiś sposób użyłbyś .pipe()(wstępnego) przefiltrowania wyniku. Wartość zwracana wywołania zwrotnego do .pipe()zostanie przekazana jako argument do funkcji donei funkcji failzwrotnej. Może również zwrócić inny obiekt odroczony, a następujące wywołania zwrotne zostaną zarejestrowane w tym odroczonym.

    Tak nie jest w przypadku .then()(lub .done(), .fail()), wartości zwracane przez zarejestrowane wywołania zwrotne są po prostu ignorowane.

Więc to nie jest tak, że używasz albo .then() lub .pipe() . Państwo mogli korzystać .pipe()z tych samych celów, co .then()jednak rozmawiać nie trzyma.


Przykład 1

Wynikiem jakiejś operacji jest tablica obiektów:

[{value: 2}, {value: 4}, {value: 6}]

i chcesz obliczyć minimalną i maksymalną wartość. Załóżmy, że używamy dwóch donewywołań zwrotnych:

deferred.then(function(result) {
    // result = [{value: 2}, {value: 4}, {value: 6}]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) {
        values.push(result[i].value);
    }
    var min = Math.min.apply(Math, values);

   /* do something with "min" */

}).then(function(result) {
    // result = [{value: 2}, {value: 4}, {value: 6}]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) {
        values.push(result[i].value);
    }
    var max = Math.max.apply(Math, values);

   /* do something with "max" */ 

});

W obu przypadkach musisz powtórzyć listę i wyodrębnić wartość z każdego obiektu.

Czy nie byłoby lepiej wcześniej jakoś wyodrębnić wartości, aby nie trzeba było tego robić osobno w obu wywołaniach zwrotnych? Tak! I to właśnie możemy wykorzystać .pipe()do:

deferred.pipe(function(result) {
    // result = [{value: 2}, {value: 4}, {value: 6}]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) {
        values.push(result[i].value);
    }
    return values; // [2, 4, 6]

}).then(function(result) {
    // result = [2, 4, 6]

    var min = Math.min.apply(Math, result);

    /* do something with "min" */

}).then(function(result) {
    // result = [2, 4, 6]

    var max = Math.max.apply(Math, result);

    /* do something with "max" */

});

Oczywiście jest to zmyślony przykład i istnieje wiele różnych (być może lepszych) sposobów rozwiązania tego problemu, ale mam nadzieję, że ilustruje to.


Przykład 2

Rozważ połączenia Ajax. Czasami chcesz zainicjować jedno wywołanie Ajax po zakończeniu poprzedniego. Jednym ze sposobów jest wykonanie drugiego połączenia w donewywołaniu zwrotnym:

$.ajax(...).done(function() {
    // executed after first Ajax
    $.ajax(...).done(function() {
        // executed after second call
    });
});

Teraz załóżmy, że chcesz oddzielić kod i umieścić te dwa wywołania Ajax wewnątrz funkcji:

function makeCalls() {
    // here we return the return value of `$.ajax().done()`, which
    // is the same deferred object as returned by `$.ajax()` alone

    return $.ajax(...).done(function() {
        // executed after first call
        $.ajax(...).done(function() {
            // executed after second call
        });
    });
}

Chciałbyś użyć obiektu odroczonego, aby zezwolić na inny kod, który wywołuje makeCallsdołączenie wywołań zwrotnych dla drugiego wywołania Ajax, ale

makeCalls().done(function() {
    // this is executed after the first Ajax call
});

nie przyniesie pożądanego efektu, ponieważ drugie wywołanie jest wykonywane wewnątrz donewywołania zwrotnego i nie jest dostępne z zewnątrz.

Rozwiązaniem byłoby użycie .pipe()zamiast tego:

function makeCalls() {
    // here we return the return value of `$.ajax().pipe()`, which is
    // a new deferred/promise object and connected to the one returned
    // by the callback passed to `pipe`

    return $.ajax(...).pipe(function() {
        // executed after first call
        return $.ajax(...).done(function() {
            // executed after second call
        });
    });
}

makeCalls().done(function() {
    // this is executed after the second Ajax call
});

Używając .pipe()możesz teraz umożliwić dołączanie wywołań zwrotnych do „wewnętrznego” wywołania Ajax bez ujawniania rzeczywistego przepływu / kolejności wywołań.


Ogólnie rzecz biorąc, odroczone obiekty stanowią interesujący sposób na oddzielenie kodu :)

Felix Kling
źródło
Ach tak, przeoczyłem, że pipemożna filtrować, thenczego nie można. Ale wydaje się, że przy wyszukiwaniu w Google tych tematów zdecydowali się nazywać to, pipea nie filterdlatego, że uważali filtrowanie za coś w rodzaju dodatkowego bonusu, który mu towarzyszył, podczas gdy pipewyraźniej wskazywało na jego prawdziwy cel. Wygląda więc na to, że oprócz filtrowania powinny istnieć inne różnice. (Z drugiej strony przyznaję, że nie rozumiem funkcji filtrowania nawet z twoimi przykładami. A result values;tak return values;przy okazji?)
hippietrail
Kiedy mówię, że nie rozumiem twoich przykładów, jest to coś takiego: W górnym przykładzie oba .then()otrzymują te same dane, w resultktórych za każdym razem filtrujesz; podczas gdy w niższym przykładzie .pipe()usuwa niektóre dane w swoim resultprzed przekazaniem ich dalej, gdy resultdwa kolejne .then()otrzymają?
hippietrail
1
@hippietrail: W międzyczasie zaktualizowałem swoją odpowiedź i uwzględniłem również inne cele .pipe(). Jeśli wywołanie zwrotne zwróci obiekt odroczony, kolejne wykonane lub zakończone niepowodzeniem wywołania zwrotne zostaną zarejestrowane dla tego obiektu. Podam inny przykład. edycja: jeśli chodzi o twój drugi komentarz: tak.
Felix Kling
Więc jedna różnica polega na tym, że dane przepływają, podczas pipe() gdy then()bardziej przypomina węzeł liścia, na końcu którego musisz użyć swoich danych i nie płynie dalej, i że pomimo faktu, że then()zwraca a, Deferrednie jest faktycznie używany / przydatny? Jeśli to prawda, pomocne może być wyjaśnienie umieszczenia czegoś podobnego /* do something with "min"/"max" */w każdej klauzuli „.then ()”.
hippietrail
1
Bez obaw :) Trochę czasu zajęło mi też pełne zrozumienie, jak działają obiekty odroczone i ich metody. Ale kiedy już to zrozumiesz, nie wydaje się to już trudne. Zgadzam się, że dokumentację można by prawdopodobnie napisać w łatwiejszy sposób.
Felix Kling
7

Nie ma przypadku, w którym MUSISZ użyć then()ponad pipe(). Zawsze możesz zignorować wartość, która pipe()zostanie przekazana. Używanie może spowodować niewielki spadek wydajności pipe- ale jest to mało prawdopodobne.

Więc może się wydawać, że możesz po prostu zawsze używać pipe()w obu przypadkach. Jednakże , za pomocą pipe(), komunikujesz się z innymi ludźmi czytania kodu (w tym siebie, sześć miesięcy od teraz), że istnieją pewne znaczenie dla wartości zwracanej. Jeśli go odrzucasz, naruszasz tę semantyczną konstrukcję.

To tak, jakby mieć funkcję, która zwraca wartość, która nigdy nie jest używana: myląca.

Więc używaj then()kiedy powinieneś i pipe()kiedy powinieneś ...

Alex Feinman
źródło
3
Znalazłem przykład z życia wzięty na blogu K. Scotta Allena „Experiments In Writing”: Geolocation, Geocoding i jQuery Promises : „W takim razie logika sterowania brzmi całkiem nieźle:” $(function () { $.when(getPosition()) .pipe(lookupCountry) .then(displayResults); }); „Zauważ, że potok różni się od to dlatego, że fajka daje nową obietnicę ”.
hippietrail
5

W rzeczywistości okazuje się, że różnica między .then()i .pipe()została uznana za niepotrzebną i zostały one wykonane tak, aby były takie same jak w wersji jQuery 1.8.

Z komentarzajaubourg w zgłoszeniu do śledzenia błędów jQuery # 11010 „MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE / A”:

W 1.8 usuniemy wtedy starą i zastąpimy ją obecną rurą. Ale bardzo smutną konsekwencją jest to, że będziemy musieli powiedzieć ludziom, aby korzystali z niestandardowego wykonania, niepowodzenia i postępu, ponieważ propozycja nie zapewnia prostego, WYDAJNEGO, czyli po prostu dodania oddzwonienia.

(emphassis mine)

hippietrail
źródło
1
Jak dotąd najlepsza referencja, szukam zaawansowanych zastosowań.
TWiStErRob