Как правильно прервать цепочку обещаний node.js, используя Q?

Я использую модуль Q для Node.js в попытках избежать "пирамиды обречения" в сценариях, где у меня много шагов. Например:

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 promises). Однако у меня есть проблема, когда мне нужно прервать цепочку задач раньше. Например, если результат1 успешно возвращен, я могу захотеть вызвать обратный вызов раньше и прервать остальные, но мои попытки сделать это не сработают...

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...".

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

Ответ 1

Любые ошибки, возникающие в цепочке обещаний, приведут к тому, что весь стек будет прерван раньше, и управление будет передано пути ошибки. (в этом случае обработчик 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 coming from somewhere else
            throw err;
        } 
    })
    .end();
}

Ответ 2

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

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

Ответ 3

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

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