2012-07-02 6 views
34

Я использую the Q module для Node.js в попытках избежать «пирамиды обречения» в сценариях, где у меня много шагов. Например:Как правильно прервать цепочку обещаний node.js, используя 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(); 
} 

По существу это, похоже, работает; если ошибка вызывается каким-либо из шагов задачи, она передается обратному вызову (хотя я был бы рад усовершенствованиям, так как я новичок в обещаниях node.js). Однако у меня есть проблема, когда мне нужно прервать цепочку задач раньше. Например, если result1 успешно вернулся я мог бы вызвать функцию обратного вызова в начале и прервать все остальное, но мои попытки сделать это не удается ...

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

В этом примере я вижу, как «отбрасывание!» и «сделать шаг 3 ...».

Я уверен, что я просто недопонимаю некоторые основные принципы здесь, поэтому буду признателен за любую помощь. Благодаря!

+0

Одно решение, которое я нашел, чтобы создать отдельное обещание цепи после первой цепи может сломаться. Чем в приведенном выше примере утверждение .then с результатом2 присоединяется к Q.ncall для шага 2 вместо того, чтобы быть привязанным к первоначальному обещанию. ОДНАКО, основным недостатком здесь является то, что он избавляется от одного из основных преимуществ для Q, на мой взгляд: избегая пирамиды гибели! Это все же лучше, чем никаких обещаний, но мне не нравится решение ... –

ответ

16

Любые ошибки, которые выбрасываются в обещание цепи заставит весь стек должен быть прерван в начале и управление передается на ошибки обратного пути. (в этом случае обработчик fail()) Когда вы обнаруживаете определенное состояние, из-за которого вы хотите прервать цепочку обещаний, просто введите очень специфическую ошибку, в которую вы попадаете в обратную ошибку и игнорируете (если вы так выберите)

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

Вы используете исключения для потока управления, и это обычно не рекомендуется. Решение, данное Крисом Ковалем, позволяет избежать этой проблемы. –

+3

'return null' не нужно после' throw' – Pepijn

34

Это случай, когда вам нужно разветвиться, что означает либо вложение, либо создание подпрограммы.

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

Или

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

Это лучший подход для разветвления? Кажется, это снова вводит отступ, когда есть несколько ветвей. Вот [пример] (https://gist.github.com/svenjacobs/3f42bbaf4cbabe2b58b5), где я выполняю несколько операций с файлами с помощью [q-io] (https://github.com/kriskowal/q-io). Сначала проверяю, существует ли каталог, список файлов, ищущих определенный файл, и удаляйте его, если найден только один соответствующий файл. Есть несколько if-условий, которые должны прервать цепочку. Я использую специальное возвращаемое значение, чтобы проверить этот случай, но нужно проверить его в каждой функции. Это хороший подход? –

+4

@SvenJacobs, что вы описываете в этом примере, является хорошим случаем для исключений. Рассмотрим https://gist.github.com/kriskowal/e98774443eb0f1653871 –

+2

У меня все еще есть проблема с этим подходом, потому что это затрудняет обработку ошибок. Бросание в цепочку обещаний (ответ Кальвина Элвина) позволяет иметь единственный '.fail()', который заботится о любой ошибке во время потока. Написание обещаний таким образом (ветвление) возвращает меня к адскому обратному обращению. – Pedro

2

Я считаю, что вам нужно отказаться только от обещания вырваться из цепочки обещаний.

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

также кажется, что .end() было изменено на .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(); 
}