2012-07-02 6 views
34

Ich benutze the Q module für Node.js in Versuchen, die "Pyramide des Schicksals" in Szenarien zu vermeiden, in denen ich viele Schritte habe. Zum Beispiel:Wie man eine node.js-Versprechungskette mit Q korrekt abbricht?

function doTask(task, callback) 
{ 
    Q.ncall(task.step1, task) 
    .then(function(result1){ 
     return Q.ncall(task.step2, task); 
    }) 
    .then(function(result2){ 
     return Q.ncall(task.step3, task); 
    }) 
    .fail(callback).end(); 
} 

Im Wesentlichen scheint dies zu funktionieren; Wenn ein Fehler von einem der Task-Schritte ausgelöst wird, wird er an den Callback übergeben (obwohl ich Verbesserungen begrüßen würde, da ich neu bei den node.js-Versprechungen bin). Ich habe jedoch ein Problem, wenn ich die Task-Chain vorzeitig abbrechen muss. Zum Beispiel erfolgreich, wenn result1 zurückgegeben könnte ich will früh den Rückruf rufen und den Rest abbrechen, aber meine Versuche, dies zu tun versagen ...

function doTask(task, callback) 
{ 
    Q.ncall(task.step1, task) 
    .then(function(result1){ 
     if(result1) 
     {// the rest of the task chain is unnecessary 
      console.log('aborting!'); 
      callback(null, result1); 
      return null; 
     } 
     return Q.ncall(task.step2, task); 
    }) 
    .then(function(result2){ 
     console.log('doing step 3...'); 
     return Q.ncall(task.step3, task); 
    }) 
    .fail(callback).end(); 
} 

In diesem Beispiel sehe ich beide „Abbruch!“ und "mache Schritt 3 ..." gedruckt.

Ich bin mir sicher, dass ich nur einige grundlegende Prinzipien hier falsch verstehe, so würde jede Hilfe zu schätzen wissen. Vielen Dank!

+0

Eine Lösung, die ich gefunden ist, eine separate Versprechen Kette zu erstellen, nachdem die erste Kette brechen könnte. Dann wird im obigen Beispiel die .then-Anweisung mit result2 an den Q.ncall für Schritt2 angehängt, anstatt an die ursprüngliche Zusage angehängt zu werden. JEDOCH ist der Hauptnachteil hier, dass er meiner Meinung nach einen der größten Vorteile für Q beseitigt: die Vermeidung der Pyramide des Verderbens! Es ist immer noch besser als gar keine Versprechungen, aber ich mag die Lösung nicht ... –

Antwort

16

Alle Fehler, die innerhalb der Versprechen Kette geworfen wird der gesamte Stapel führen zu früh abgebrochen werden und die Steuerung wird auf den Fehler zurück Weg gegeben. (In diesem Fall, der fail() - Handler) Wenn Sie einen bestimmten Zustand entdecken, der Sie dazu veranlasst, die Versprechungskette abzubrechen, dann werfen Sie einfach einen sehr spezifischen Fehler, den Sie in den Fehler-Trap einfangen und ignorieren (wenn Sie das tun) Wählen Sie)

function doTask(task, callback) 
{ 
    Q.ncall(task.step1, task) 
    .then(function(result1){ 
     if(result1 == 'some failure state I want to cause abortion') 
     {// the rest of the task chain is unnecessary 
      console.log('aborting!'); 
      throw new Error('abort promise chain'); 
      return null; 
     } 
     return Q.ncall(task.step2, task); 
    }) 
    .then(function(result2){ 
     console.log('doing step 3...'); 
     return Q.ncall(task.step3, task); 
    }) 
    .fail(function(err) { 
     if (err.message === 'abort promise chain') { 
      // just swallow error because chain was intentionally aborted 
     } 
     else { 
      // else let the error bubble up because it's coming from somewhere else 
      throw err; 
     } 
    }) 
    .end(); 
} 
+17

Sie verwenden Ausnahmen für den Kontrollfluss, und das wird normalerweise nicht empfohlen. Die Lösung von Kris Kowal vermeidet dieses Problem. –

+3

'Return null' ist nicht notwendig nach dem' throw' – Pepijn

34

Dies ist ein Fall, in dem Sie verzweigen müssen, was entweder das Verschachteln oder das Erstellen eines Unterprogramms bedeutet.

function doTask(task, callback) { 
    return Q.ncall(task.step1, task) 
    .then(function(result1) { 
     if (result1) return result1; 
     return Q.ncall(task.step2, task) 
     .then(function(result2) { 
      return Q.ncall(task.step3, task); 
     }) 
    }) 
    .nodeify(callback) 
} 

Oder

function doTask(task, callback) { 
    return Q.ncall(task.step1, task) 
    .then(function(result1) { 
     if (result1) { 
      return result1; 
     } else { 
      return continueTasks(task); 
     } 
    }) 
    .nodeify(callback) 
} 

function continueTasks(task) { 
    return Q.ncall(task.step2, task) 
    .then(function(result2) { 
     return Q.ncall(task.step3, task); 
    }) 
} 
+0

Ist das der beste Ansatz für die Verzweigung? Es scheint, dass dies erneut Einrückung einführt, wenn mehrere Zweige vorhanden sind. Hier ist ein [Beispiel] (https://gist.github.com/svenjacobs/3f42bbaf4cbabe2b58b5), wo ich mehrere Dateioperationen mit [q-io] (https://github.com/kriskowal/q-io) durchführe. Ich überprüfe zuerst, ob ein Verzeichnis existiert, listet die Dateien auf, die nach einer bestimmten Datei suchen, und lösche sie, wenn nur eine passende Datei gefunden wird. Es gibt mehrere Wenn-Bedingungen, die die Kette abbrechen sollten. Ich verwende einen speziellen Rückgabewert, um diesen Fall zu überprüfen, muss ihn aber in jeder Funktion überprüfen. Ist das ein guter Ansatz? –

+4

@SvenJacobs, was Sie in diesem Beispiel beschreiben, ist ein guter Fall für Ausnahmen. Betrachten Sie https://gist.github.com/kriskowal/e98774443eb0f1653871 –

+2

Ich habe immer noch ein Problem mit diesem Ansatz, weil es schwieriger für die Fehlerbehandlung macht. Das Werfen in eine Versprechungskette (Calvin Alvins Antwort) erlaubt es, ein einziges '.fail()' zu haben, das sich um irgendeinen Fehler während des Ablaufs kümmert. Das Schreiben auf diese Art (Verzweigen) bringt mich zurück in die Callback-Hölle. – Pedro

2

Ich glaube, Sie müssen nur das Versprechen ablehnen, aus der Versprechungskette zu brechen.

https://github.com/kriskowal/q/wiki/API-Reference#qrejectreason

auch scheint es, wie .end() geändert wurde .done()

function doTask(task, callback) 
{ 
    Q.ncall(task.step1, task) 
    .then(function(result1){ 
     if(result1) 
     {// the rest of the task chain is unnecessary 
      console.log('aborting!'); 
      // by calling Q.reject, your second .then is skipped, 
      // only the .fail is executed. 
      // result1 will be passed to your callback in the .fail call 
      return Q.reject(result1); 
     } 
     return Q.ncall(task.step2, task); 
    }) 
    .then(function(result2){ 
     console.log('doing step 3...'); 
     return Q.ncall(task.step3, task); 
    }) 
    .fail(callback).done(); 
} 
Verwandte Themen