2015-01-22 2 views
8

Ich benutze Node.js und Bluebird, um eine ziemlich komplizierte Logik zu erstellen, die das Dekomprimieren einer strukturierten Datei, das Analysieren von JSON, das Erstellen und Vornehmen von Änderungen an mehreren MongoDB-Dokumenten und das Schreiben verwandter Dateien an mehreren Speicherorten umfasst. Ich habe auch ziemlich komplizierte Fehlerbehandlung für alle diese abhängig von dem Zustand des Systems, wenn ein Fehler auftritt.Verwalten von Abhängigkeiten von Versprechen

Ich habe Schwierigkeiten, über einen guten Weg nachzudenken, Abhängigkeiten durch den Fluss von Versprechen zu verwalten.

Mein bestehender Code sieht im Grunde wie folgt:

var doStuff = function() { 
    var dependency1 = null; 
    var dependency2 = null; 

    promise1() 
    .then(function (value) { 
    dependency1 = value; 

    return promise2() 
    .then(function (value) { 
     dependency2 = value; 

     return promise3(dependency1) 
     .then(successFunction); 
    }); 
    }) 
    .catch(function (err) { 
    cleanupDependingOnSystemState(err, dependency1, dependency2); 
    }); 
}; 

Beachten Sie, dass abhaengigkeit1 nicht bis promise3 erforderlich ist, und dass die Fehler-Handler muss über die Abhängigkeiten kennen.

Für mich scheint dies wie Spaghetti-Code (und meine eigentliche Code ist viel schlimmer mit viel paralleler Kontrollfluss). Ich habe auch gelesen, dass das Zurückgeben eines anderen Versprechens innerhalb eines Callbacks ein Antipattern ist. Gibt es einen besseren/saubereren Weg, um das zu erreichen, was ich versuche?

+0

Dies ist wahrscheinlich besser für Programmierer.stackexchange.com, um ehrlich zu sein, Mann. – 1252748

+0

Hängt 'promise2' davon ab, dass' promise1' beendet ist? Der Code impliziert das, aber es ist ein wenig unklar. – loganfsmyth

+0

Ja, jedes Versprechen, das später kommt, hängt von etwas ab, was ein früheres Versprechen gemacht hat/Daten, die es abgerufen hat. –

Antwort

8

Ich finde beide Antworten derzeit nett aber ungeschickt zur Verfügung gestellt. Sie sind beide gut, aber enthalten Overhead, ich glaube nicht, dass du es haben musst. Wenn Sie stattdessen Versprechen als Proxies verwenden, erhalten Sie viele Dinge kostenlos.

var doStuff = function() { 
    var p1 = promise1(); 
    var p2 = p1.then(promise2); 
    var p3 = p1.then(promise3); // if you actually need to wait for p2 here, do. 
    return Promise.all([p1, p2, p3]).catch(function(err){ 
     // clean up based on err and state, can unwrap promises here 
    }); 
}; 

Bitte verwenden Sie nicht successFunction und so ist es ein Anti-Muster und verliert Informationen. Wenn Sie das Gefühl, Sie verwenden müssen successFunction können Sie schreiben:

var doStuff = function() { 
    var p1 = promise1(); 
    var p2 = p1.then(promise2); 
    var p3 = p1.then(promise3); // if you actually need to wait for p2 here, do. 
    Promise.join(p1, p2, p3, successFunction).catch(function(err){ 
     // clean up based on err and state, can unwrap promises here 
    }); 
}; 

Es ist aber unendlich viel schlimmer, da es nicht die Verbraucher Griff Fehler lassen sie in der Lage sein kann zu behandeln.

+0

Ich denke, es sollte "endlich" nicht "fangen", sonst wird Cleanup nicht in successcase getan – Esailija

+0

@Esailija sein Originalbeispiel hat Fang, aber wenn es Ressourcenbereinigung ist stimme ich schließlich ist der richtige Weg zu gehen. Wahrscheinlich noch besser, ein Entsorgermuster zu verwenden oder besser noch einen tatsächlichen '.disposer' –

+0

Dies ist genau die Art von Lösung, nach der ich gesucht hatte, ich hatte nicht in Betracht gezogen, mehrere. Callbacks zu registrieren (es scheint jetzt so offensichtlich). Ich habe viel Refactoring zu tun. Vielen Dank. –

1

könnte diese Frage für code review sinnvoll sein, mehr aber hier ist, wie ich es in diesem Beispiel gegeben nähern würde:

var doStuff = function() { 
    // Set up your promises based on their dependencies. In your example 
    // promise2 does not use dependency1 so I left them unrelated. 
    var dep1Promise = promise1(); 
    var dep2Promise = promise2(); 
    var dep3Promise = dependency1Promise.then(function(value){ 
    return promise3(value); 
    }); 

    // Wait for all the promises the either succeed or error. 
    allResolved([dep1Promise, dep2Promise, dep3Promise]) 
     .spread(function(dep1, dep2, dep3){ 

    var err = dep1.error || dep2.error || dep3.error; 
    if (err){ 
     // If any errored, call the function you prescribed 
     cleanupDependingOnSystemState(err, dep1.value, dep2.value); 
    } else { 
     // Call the success handler. 
     successFunction(dep3.value); 
    } 
}; 

// Promise.all by default just fails on the first error, but since 
// you want to pass any partial results to cleanupDependingOnSystemState, 
// I added this helper. 
function allResolved(promises){ 
    return Promise.all(promises.map(function(promise){ 
    return promise.then(function(value){ 
     return {value: value}; 
    }, function(err){ 
     return {error: err}; 
    }); 
    }); 
} 

Die Verwendung von allResolved nur wegen Ihres Rückruf Besonderheiten ist, wenn Sie eine allgemeinere haben Fehlerbehandlung, können Sie einfach lösen Promise.all direkt verwenden, oder sogar:

var doStuff = function() { 
    // Set up your promises based on their dependencies. In your example 
    // promise2 does not use dependency1 so I left them unrelated. 
    var dep1Promise = promise1(); 
    var dep2Promise = promise2(); 
    var dep3Promise = dependency1Promise.then(function(value){ 
    return promise3(value); 
    }); 

    dep3Promise.then(successFunction, cleanupDependingOnSystemState); 
}; 
1

Es ist sicherlich nicht ein Antipattern Versprechen zurückzukehren innerhalb then s, verschachtelte Versprechen Abflachung ist ein Feature der Versprechensspezifikation.

Hier ist eine mögliche Rewrite, obwohl ich es nicht sicher bin, ist sauberer:

var doStuff = function() { 

    promise1() 
    .then(function (value1) { 

    return promise2() 
    .then(function (value2) { 

     return promise3(value1) 
     .then(successFunction) 
     .finally(function() { 
     cleanup(null, value1, value2); 
     }); 

    }) 
    .finally(function() { 
     cleanup(null, value1, null); 
    }); 

    }) 
    .finally(function() { 
    cleanup(null, null, null); 
    }); 

}; 

Oder eine andere Option, mit atomarer Bereinigungsfunktionen:

var doStuff = function() { 

    promise1() 
    .then(function (value1) { 

    return promise2() 
    .then(function (value2) { 

     return promise3(value1) 
     .then(successFunction) 
     .finally(function() { 
     cleanup3(value2); 
     }); 

    }) 
    .finally(function() { 
     cleanup2(value1); 
    }); 

    }) 
    .finally(function (err) { 
    cleanup1(err); 
    }); 

}; 

Wirklich, ich fühle mich wie Sie gibt es nicht viel kann tun, um das aufzuräumen. Event mit Vanille try/catch es, das bestmögliche Muster ist diesen sehr ähnlich.

+0

Ich denke, ich habe das Gefühl, dass mir etwas an Versprechen fehlt, weil die meisten Dokumentationsbeispiele die extrem saubere verkettete Form sind. –