2015-01-22 32 views
8

Sto utilizzando Node.js e Bluebird per creare una logica abbastanza complicata che implica la decompressione di un file strutturato, l'analisi di JSON, la creazione e la modifica di diversi documenti MongoDB e la scrittura di file correlati in più posizioni. Ho anche una gestione degli errori abbastanza complicata per tutto questo a seconda dello stato del sistema quando si verifica un errore.Gestione delle dipendenze delle promesse

Ho difficoltà a pensare a un buon modo per gestire le dipendenze attraverso il flusso di promesse.

mio codice esistente appare sostanzialmente in questo modo:

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

Nota che Dependency1 non è necessario fino promise3, e che il gestore degli errori ha bisogno di conoscere le dipendenze.

A me sembra un codice spaghetti (e il mio codice effettivo è molto peggiore con un flusso di controllo parallelo molto elevato). Ho anche letto che restituire un'altra promessa all'interno di una. Callback è un antipattern. Esiste un modo migliore/più pulito per realizzare ciò che sto cercando di fare?

+0

Questo è probabilmente meglio per programmers.stackexchange.com per essere un uomo onesto. – 1252748

+0

'promise2' dipende dal completamento di' promise1'? Il codice lo sottintende, ma è un po 'oscuro. – loganfsmyth

+0

Sì, qualsiasi promessa successiva dipenderà da qualcosa che una promessa precedente ha fatto/dati che ha recuperato. –

risposta

8

Trovo che entrambe le risposte siano fornite in modo soddisfacente ma goffe. Sono entrambi buoni ma contengono un sovraccarico che non credo sia necessario avere. Se invece usi le promesse come proxy, ottieni molte cose gratis.

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

prega di non utilizzare successFunction e tale è un anti-modello e perde le informazioni. Se ti senti come si deve usare successFunction si può scrivere:

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

Tuttavia, è infinitamente peggiore dal momento che non lascerà gli errori manico di consumo che possono essere in grado di gestire.

+0

Penso che dovrebbe essere 'finally' not' catch', altrimenti la pulizia non viene eseguita nel successore – Esailija

+0

@Esailija il suo esempio originale ha catch, ma se si tratta di pulizia delle risorse sono d'accordo, infine, è la strada giusta da percorrere. Probabilmente ancora meglio usare un pattern di dissipatore o anche un vero '.disposer' –

+0

Questo è esattamente il tipo di soluzione che stavo cercando, non avevo considerato di registrare più callback (così sembra ora così ovvio). Ho molto da fare per il refactoring. Grazie. –

1

Questa domanda potrebbe essere più appropriato per code review ma ecco come avrei avvicino dato questo esempio:

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

L'uso di allResolved è solo a causa delle vostre specifiche di callback, se si ha un più generale gestore degli errori, si potrebbe semplicemente risolvere utilizzando Promise.all direttamente, o anche:

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

e non è certo un antipattern per tornare promesse entro then s, appiattimento promesse nidificati è una caratteristica della promessa spec.

Ecco una possibile riscrittura, anche se non sono sicuro che sia più pulito:

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

}; 

o un'altra opzione, con funzioni di pulizia atomiche:

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

}; 

Davvero, mi sento come non c'è molto che si può fare per pulire questo. Evento con vaniglia try/catch es, il miglior schema possibile è abbastanza simile a questi.

+0

Immagino di essermi sentito come se mi mancasse qualcosa sulle promesse perché la maggior parte degli esempi di documentazione sono la forma estremamente pulita. –

Problemi correlati