2012-07-02 13 views
34

Sto utilizzando the Q module per Node.js nei tentativi di evitare la "piramide di sventura" in scenari in cui ho molti passaggi. Ad esempio:Come interrompere correttamente una catena di promemoria node.js utilizzando Q?

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

In sostanza, questo sembra funzionare; se viene generato un errore da una qualsiasi delle fasi dell'attività, viene passato al callback (anche se sarei benvenuto a miglioramenti, dato che sono nuovo alle promesse di node.js). Tuttavia, ho un problema quando ho bisogno di abortire presto la catena di attività. Ad esempio, se risultato1 viene restituito con successo potrei voler chiamare la callback presto e annullare il resto, ma i miei tentativi in ​​questo senso sono in mancanza di ...

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 questo esempio, vedo entrambi "abortire!" e "facendo il punto 3 ..." stampato.

Sono sicuro che sto semplicemente fraintendendo alcuni principi di base qui, quindi apprezzerei qualsiasi aiuto. Grazie!

+0

Una soluzione che ho trovato è quello di creare una catena di promessa separata dopo la prima catena potrebbe rompersi. Quindi, nell'esempio precedente, l'istruzione .then con result2 viene allegata a Q.ncall per step2, anziché essere allegata alla promessa originale. TUTTAVIA, lo svantaggio principale qui è che si sbarazza di uno dei maggiori vantaggi per Q a mio parere: evitando la piramide del destino! È ancora meglio di nessuna promessa, ma non mi piace la soluzione ... –

risposta

16

eventuali errori che vengono gettati all'interno della catena promessa causerà l'intero stack di essere interrotta in anticipo e il controllo è dato al percorso di errore-back. (in questo caso, il gestore fail()) Quando rilevi un certo stato che ti costringe ad abortire la catena di promessa, allora fai un errore molto specifico, che intrappoli nel back-error e ignori (se lo fai tu scegliere)

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

Stai usando le eccezioni per il controllo del flusso, e questo di solito non è raccomandato. La soluzione fornita da Kris Kowal evita questo problema. –

+3

'return null' non è necessario dopo il' throw' – Pepijn

34

Questo è un caso in cui è necessario eseguire la diramazione, che significa nidificare o creare una subroutine.

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

O

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

È questo l'approccio migliore per la ramificazione? Sembra che questo introduca nuovamente il rientro quando ci sono più rami. Ecco un [esempio] (https://gist.github.com/svenjacobs/3f42bbaf4cbabe2b58b5) in cui eseguo più operazioni sui file utilizzando [q-io] (https://github.com/kriskowal/q-io). Per prima cosa controlla se esiste una dir, elenca i file alla ricerca di un determinato file e la cancella se viene trovato un solo file corrispondente. Ci sono più condizioni se là che dovrebbero interrompere la catena. Io uso un valore di ritorno speciale per verificare il caso, ma devo verificarlo in ogni funzione. è un buon approccio? –

+4

@SvenJacobs quello che stai descrivendo in quell'esempio è un buon caso per le eccezioni. Considera https://gist.github.com/kriskowal/e98774443eb0f1653871 –

+2

Ho ancora un problema con questo approccio perché rende più difficile la gestione degli errori. Il lancio all'interno di una catena di promesse (la risposta di Calvin Alvin) consente di avere un singolo '.fail()' che si prende cura di qualsiasi errore durante il flusso. Scrivere promette in questo modo (ramificarsi) mi riporta all'inferno del callback. – Pedro

2

Credo che si debba solo respingere la promessa di uscire dalla catena di promesse.

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

anche Sembra .end() è stato cambiato in .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(); 
}