2012-07-02 15 views
34

Estoy usando the Q module para Node.js en un intento de evitar la "pirámide de fatalidad" en escenarios en los que tengo muchos pasos. Por ejemplo:Cómo abortar correctamente una cadena de promesa node.js usando 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(); 
} 

Esencialmente esto parece funcionar; si se produce un error en alguno de los pasos de la tarea, se pasa a la devolución de llamada (aunque sería bienvenido a las mejoras, ya que soy nuevo en las promesas de node.js). Sin embargo, tengo un problema cuando necesito abortar la cadena de tareas temprano. Por ejemplo, si se devuelve con éxito resultado1 puede ser que quiera llamar a la devolución de llamada temprana y abortar el resto, pero mis intentos de hacerlo no se ...

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

En este ejemplo, veo tanto "abortar" y "haciendo el paso 3 ..." impreso.

Estoy seguro de que estoy simplemente malinterpretando algunos principios básicos aquí, por lo que agradecería cualquier ayuda. ¡Gracias!

+0

Una solución que encontré es crear una cadena promesa separada después de la primera cadena se puede romper. De lo que se trata, en el ejemplo anterior, la instrucción .then con result2 se adjunta a Q.ncall para el paso 2, en lugar de adjuntarse a la promesa original. SIN EMBARGO, la desventaja principal aquí es que se deshace de uno de los principales beneficios de Q en mi opinión: ¡evitar la pirámide de la fatalidad! Todavía es mejor que no tener ninguna promesa, pero no me gusta la solución ... –

Respuesta

16

Cualquier error que se tiran dentro de la cadena promesa hará que toda la pila sea abortado temprano y se le da el control de la ruta de error de devolución. (en este caso, el controlador fail()) Cuando detecta un determinado estado que hace que desee abortar la cadena de la promesa, entonces simplemente arroje un error muy específico, que atrapa en el error e ignora (si lo hace elija)

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

Está utilizando excepciones para el flujo de control, y esto generalmente no se recomienda. La solución dada por Kris Kowal evita ese problema. –

+3

'return null' no es necesario después del' throw' – Pepijn

34

Este es un caso en el que tendrá que bifurcar, lo que significa anidar o crear una subrutina.

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

¿Es este el mejor enfoque para la bifurcación? Parece que esto introduce una sangría nuevamente cuando hay múltiples ramas. Aquí hay un [ejemplo] (https://gist.github.com/svenjacobs/3f42bbaf4cbabe2b58b5) donde realizo múltiples operaciones de archivos usando [q-io] (https://github.com/kriskowal/q-io). Primero compruebo si existe un directorio, lista los archivos que buscan un determinado archivo y lo elimino si solo se encuentra un archivo coincidente. Hay múltiples condiciones if allí que deberían abortar la cadena. Utilizo un valor de retorno especial para verificar ese caso, pero tengo que verificarlo en cada función. ¿Es este un buen enfoque? –

+4

@SvenJacobs lo que está describiendo en ese ejemplo es un buen caso para las excepciones. Considere https://gist.github.com/kriskowal/e98774443eb0f1653871 –

+2

Todavía tengo un problema con este enfoque porque dificulta el manejo de errores. Al lanzar dentro de una cadena de promesas (la respuesta de Calvin Alvin), se permite tener un solo '.fail()' que se ocupa de cualquier error durante el flujo. Escribir las promesas de esta manera (ramificación) me lleva de vuelta al infierno de devolución de llamada. – Pedro

2

Creo que solo tiene que rechazar la promesa de salir de la cadena de la promesa.

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

también parece que .end() ha sido cambiado a .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(); 
} 
Cuestiones relacionadas