Wie verkette ich drei asynchrone Aufrufe mit jQuery-Versprechungen?

69

Ich habe drei HTTP-Anrufe, die ich synchron tätigen muss, und wie übergebe ich Daten von einem Anruf zum anderen?

function first()
{
   ajax()
}

function second()
{
   ajax()
}

function third()
{
   ajax()
}


function main()
{
    first().then(second).then(third)
}

Ich habe versucht, die verzögerte Funktion für die beiden Funktionen zu verwenden, und eine Teillösung gefunden. Kann ich es auf drei Funktionen erweitern?

function first() {
    var deferred = $.Deferred();
     $.ajax({

             "success": function (resp)
             {

                 deferred.resolve(resp);
             },

         });
    return deferred.promise();
}

function second(foo) {
     $.ajax({
            "success": function (resp)
            {
            },
            "error": function (resp)
            {
            }
        });
}


first().then(function(foo){second(foo)})
John Mcdock
quelle
Sieht aus wie ein genaues Duplikat von Wie man Ajax-Anrufe mit jquery verkettet
jfriend00
1
snikerjQuery.Promise (). Promise (). Promise (nichtig)
Jay
1
Die Lösung in der "doppelten" Frage ist, dass ich alt und obselet denke.
John Mcdock
1
Ja, aber der Vorschlag, q aus dem Update zu verwenden, ist gut: github.com/kriskowal/q . Für kompliziertere Dinge ist seq auch einen Blick wert: github.com/substack/node-seq
Milimetric

Antworten:

84

Geben Sie in jedem Fall das von zurückgegebene jqXHR-Objekt zurück $.ajax().

Diese Objekte sind Promise-kompatibel und können daher mit .then()/ .done()/ .fail()/ verkettet werden .always().

.then() ist die, die Sie in diesem Fall wollen, genau wie in der Frage.

function first() {
   return $.ajax(...);
}

function second(data, textStatus, jqXHR) {
   return $.ajax(...);
}

function third(data, textStatus, jqXHR) {
   return $.ajax(...);
}

function main() {
    first().then(second).then(third);
}

Argumente data, textStatusund jqXHRaus dem entstehen $.ajax()in dem vorherigen Funktionsaufruf, dh. first()Feeds second()und second()Feeds third().

DEMO (mit$.when('foo')der Erfüllung eines erfüllten Versprechens anstelle von$.ajax(...)).

Rote Beete-Rote Beete
quelle
7
Das funktioniert bei mir nicht. Wenn ich Ihnen ein Beispiel versuche, werden die Ergebnisse vom ersten zum zweiten UND dritten übergeben. Also füttert zuerst der zweite und der dritte. Nicht der erste füttert den zweiten, der zweite den dritten. $ .ajax ({...}). then (function (..) {return $ .ajax ({...});}). then (function (...) {/ * Ich möchte die Ergebnisse von der zweite Anruf. * ​​/});
Jerinaw
1
Momentan keine Zeit, einen Test durchzuführen, aber ich kann nicht verstehen, warum der Code in meiner Antwort nicht funktionieren sollte. Stellen Sie sicher, dass Sie jQuery 1.8.3 oder höher verwenden. Frühere Versionen geben möglicherweise das von Ihnen beschriebene Symptom an.
Rote Beete-Rote Beete
2
Nur mit jquery 1.11.0 überprüft und alle Aufrufe erhalten Argumente von wann und nicht von vorher.
Pyjics
1
@pajics, ich habe der Antwort eine Demo hinzugefügt. Entweder wurde 1.11.0 zurückgesetzt oder Sie machen etwas falsch. Können Sie eine Geige liefern?
Rote Beete-Rote Beete
1
Leider kann ich meinen Code nicht mit fiddle (mit gmaps) neu erstellen, aber als ich etwas Einfaches wie dieses ausprobierte : jsfiddle.net/CbPbw/1 funktionierte es wie erwartet. Ich muss herausfinden, was mit meinem Code nicht stimmt. Danke
pajics
39

Es gibt tatsächlich einen viel einfacheren Ansatz, wenn Versprechen mit jQuery verwendet werden. Schauen Sie sich Folgendes an:

$.when(
    $.ajax("/first/call"),
    $.ajax("/second/call"),
    $.ajax("/third/call")
    )
    .done(function(first_call, second_call, third_call){
        //do something
    })
    .fail(function(){
        //handle errors
    });

Verketten Sie einfach alle Ihre Aufrufe mit dem Aufruf $ .when (...) und verarbeiten Sie die Rückgabewerte im Aufruf .done (...).

Hier ist eine exemplarische Vorgehensweise, wenn Sie es vorziehen: http://collaboradev.com/2014/01/27/understanding-javascript-promises-in-jquery/

user3242460
quelle
9
@Emilio, ja genau. Das hat das OP gefordert, und diese Antwort liefert diesbezüglich keine Antwort.
Rote Beete-Rote Beete
16
Nicht die Antwort, nach der das OP suchte, sondern die, die ich war. Vielen Dank!
Daniel Buckmaster
9
Beantwortet eine ganz andere Frage. Empfehlen Sie das Entfernen, um Verwirrung zu vermeiden.
Derek Henderson
16
Die drei Ajax-Aufrufe in diesem Beispiel werden asynchron ausgeführt
jchook
7
Diese Antwort verkettet die Anforderungen nicht, sondern wird parallel ausgeführt.
Arturo Torres Sánchez
30

Ziemlich spät zu antworten, aber ich denke, bei den Antworten fehlt ein direkter Code zum Verketten. Das Verketten von Ereignissen ist ziemlich einfach und verspricht Unterstützung bei der Abfrage. Ich benutze folgendes zum Verketten:

$.ajax()
.then(function(){
   return $.ajax() //second ajax call
})
.then(function(){
   return $.ajax() //third ajax call
})
.done(function(resp){
   //handle final response here
 })

Es ist einfach und nicht kompliziert für Schleifen oder mehrere verschachtelte Rückrufe.

prajnavantha
quelle
14

Es ist viel einfacher als das.

$.ajax gibt bereits ein Versprechen zurück (Zurückgestelltes Objekt), sodass Sie einfach schreiben können

function first() {
    return $.ajax(...);
}
SLaks
quelle
@SLaks Ist es möglich, dass mehr als eine aufgeschoben wird? Wie in first().then(second, third).then(fourth);?
Mark Pieszak - Trilon.io
2
Wie verkette ich mehrere Ajax-Aufrufe und übergebe den Rückgabewert? deferred.resolve war früher so, wie ich es gemacht habe.
John Mcdock
@JohnMcdock: Gibt einen Wert von .then()(in 1.8+) zurück. Siehe api.jquery.com/deferred.then
SLaks
5
@SLaks - Bitte erweitern Sie Ihre Antwort, um besser zu erklären, was gefragt wird, da Ihre Antwort für die Natur und die Grundlagen des jQuery-Ajax-Objekts sehr wichtig zu sein scheint. Sie verdient ein paar weitere Zeilen, in denen erklärt wird, wie mehrere asynchrone Funktionen
verkettet werden
6

Sie können es funktionaler schreiben:

[function() { return ajax(...)}, function(data) { return ajax(...)}]
.reduce(function(chain, callback) { 
  if(chain) { 
    return chain.then(function(data) { return callback(data); });
  } else {
    return callback();
  }
}, null)
Mike Vitik
quelle
5

Der beste Weg, dies zu tun, besteht darin, eine wiederverwendbare Funktion dafür zu erstellen. Dies kann sogar mit nur einer Codezeile erfolgen, indem reduce:

function chainPromises(list) {
    return list.reduce((chain, func) => chain ? chain.then(func) : func(), null);
}

Diese Funktion akzeptiert eine Reihe von Rückrufen, die wie Ihre drei Funktionen ein Versprechungsobjekt zurückgeben.

Anwendungsbeispiel:

chainPromises([first, second, third]).then(function (result) {
    console.log('All done! ', result);
});

Auf diese Weise ist das Ergebnis von firstautomatisch auch der Parameter von second. Im Grunde genommen geschieht also Folgendes:

first().then(function(res1) { return second(res1) })
       .then(function(res2) { return third(res2)  })
       .then(function(result) { console.log('All done! ', result) });

Und natürlich können Sie dem Array so viele Funktionen hinzufügen, wie Sie möchten.

Duncan Luk
quelle
1
Mit Abstand die beste Lösung. Ich nahm das Snippet und integrierte es in eine Bibliothek, die Gruppierungen verketteter Ajax-Ereignisse erfordert. Leider musste ich die ES6-Syntax ändern, aber immer noch eine saubere Lösung. Gut gemacht. Ich hoffe, dass mehr Leute darüber abstimmen.
Nick Johnson
4

Ich habe hier eine gut aussehende Lösung gefunden: Wie verkette ich eine Folge von verzögerten Funktionen in jQuery 1.8.x?

Und hier ist meine eigene Implementierung eines ähnlichen Ansatzes, etwas hässlich, aber wahrscheinlich funktionierend. Das Ergebnis jeder Methode wird als «Fortschrittsaktualisierung» für das zurückgegebene Versprechenobjekt gesendet.

  $.chain = function() {
      var defer = $.Deferred();
      var funcs = arguments;
      var left = funcs.length;
      function next(lastResult) {
          if(left == 0) {
              defer.resolve();
              return;
          }
          var func = funcs[funcs.length - left]; // current func
          var prom = func(lastResult).promise(); // for promise will return itself,
                                       // for jquery ojbect will return promise.
          // these handlers will be launched in order we specify them
          prom.always(function() {
              left--;
          }).done(function(ret) {
              defer.notify({
                  idx: funcs.length-left,
                  left: left,
                  result: ret,
                  success: true,
              });
          }).fail(function(ret) {
              defer.notify({
                  idx: funcs.length-left,
                  left: left,
                  result: ret,
                  success: false,
              });
          }).always(function(ret) {
              next(ret);
          });
      }
      next();
      return defer.promise();
  };

Wie kann ich es für Ihre Situation verwenden? Vielleicht nicht schön, aber es sollte funktionieren:

function first() {
    return ajax(...);
}

var id;

funciton second() {
    return ajax(id, ...);
}

function third() {
    return ajax(id, ...);
}

$.chain(first, second, third).progress(function(p) {
    if(p.func == first)
        id = p.result.identifier;
}).then(function() {
    alert('everything is done');
});

Oder Sie können diese ID-Variable einfach über die firstFunktion zuweisen .

Wenn Sie nur das Ergebnis der vorherigen Funktion benötigen, können Sie diesen Ansatz verwenden:

function first() {
    return ajax(...);
}
function second(first_ret) {
    return ajax(first_ret.id, ...);
}
function third(second_ret) {
    return ajax(second_ret.something, ...);
}
MarSoft
quelle
Wenn Sie eine notify()Methode für ein DeferredObjekt aufrufen , übergeben Sie ihm ein Objekt. Dann alle , die Updates Fortschritte abonniert jenes Deferred‚s promisewird mit dem Objekt aufgerufen werden.
MarSoft
Es hat bei mir funktioniert! Ich musste nur die Funktion next () ändern, um next (ret ) zu funktionieren , da ret nicht definiert wurde :)
ilian6806
Danke @ ilian6806, behoben.
MarSoft
1

Folgendes scheint zu funktionieren und ermöglicht es, dass die Liste der Funktionen dynamisch ist:

<html>
  <head>
  <title>demo chained synchronous calls</title>
  </head>
  <body>

  <script src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
  <script type="text/javascript">
    function one(parms) {
        console.log('func one ' + parms);
        return 1;
    }

    function two(parms) {
        console.log('func two ' + parms);
        return 2;
    }

    function three(parms) {
        console.log('func three ' + parms);
        return 3;
    }

    function four(parms) {
        console.log('func four ' + parms);
        return 4;
    }

    var funcs = ['one', 'two', 'three', 'four'];
    var rvals = [0];

    function call_next_func() {
        if (funcs.length == 0) {
            console.log('done');
        } else {
            var funcname = funcs.shift();
            console.log(funcname);
            rvals.push(window[funcname](rvals));
            call_next_func();
        }
    }

    $(document).ready(function($){
        call_next_func();
    });
  </script>

  </body>
</html>

duanev
quelle
-1

Um jquery Ajax-Anrufe zu verketten, habe ich Folgendes getan:

function A(){
     return $.ajax({
      url: url,
      type: type,
      data: data,
      datatype: datatype,
      success: function(data)
      {
        code here
      }
    });
   }

   function B(){
     return $.ajax({
      url: url,
      type: type,
      data: data,
      datatype: datatype,
      success: function(data)
      {
        code here
      }
    });
   }

   function C(){
     return $.ajax({
      url: url,
      type: type,
      data: data,
      datatype: datatype,
      success: function(data)
      {
        code here
      }
    });
   }

   A().done(function(data){
     B().done(function(data){
        C();
     })
   });
Dimitris Kougioumtzis
quelle
-4

Ich hatte gerade das gleiche Problem, Ajax-Anrufe zu verketten. Nachdem ich ein paar Tage lang versucht hatte, tat ich endlich $.ajax({ async: false,... das, was ich erreichen wollte. Daran habe ich einfach nicht gedacht. Es könnte anderen helfen ...

MelP
quelle
1
Normalerweise eine schlechte Idee. Ihr Thread wird blockiert, sodass Sie nichts weiter tun können, bis die Anforderung abgeschlossen ist. Dies führt im Allgemeinen zu einer schlechten UX
Charlie Martin
Du liegst absolut richtig. Aber das ist das Verhalten, das ich wollte. Ich erweitere eine Reihe unvorhersehbarer Elemente, die je nach Existenz eines Baumes wie ein Baum aussehen.
MelP
2
Das könnte leicht asynchron gemacht werden. Chrome gibt sogar eine Warnung aus, wenn Sie dies jetzt synchron tun ... Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check http://xhr.spec.whatwg.org/.
Charlie Martin