Async: breaking the waterfall chain without throwing an error

Created on 15 Feb 2011  ·  8Comments  ·  Source: caolan/async

I was told I need to refactor my code that is using async waterfall. I used it primarily because I had several chained functions that were going too deep, and I wanted to reuse a function, etc. There is a condition at one point down the chain that is not strictly an error condition, but that I don't want to pass down the rest of the waterfall chain, because it's useless - say for example I did a query and there are no results, and that's not an error, I don't want to propagate all the way down with no results. If I were doing this without async, I guess I would have a "final" callback and which could be called to break out of the chain without explicitly throwing an error. This functionality doesn't seem to be present in waterfall. Is there a way to do this cleanly? The only way I can see to do it is to throw a special error and then handle that on the final callback so as to return a non error.

Most helpful comment

Personally I find myself needing functionality like this and think it should be added to the core library.

Here is my helper workaround if anyone is interested...

exports.breakWaterfall = function(tasks, callback){
    async.waterfall(tasks, function(){
        if(arguments[0] === 'break'){
            arguments[0] = null;
        }
        callback.apply(null, arguments);
    });
}

If you need to break to the last function within a task just call the callback like this: done('break', other, arguments);.

The helper just detects the 'break' and mutates the arguments so it does not look like an error to the rest of your code.

All 8 comments

Hi,

you probably solved your issue by now, btw this is my approach:

var flow = [
    async.apply(...),
    // ...
];

async.waterfall(flow, function (err) {
    if (err === 'ok') return;
    // handle error
});

When a function calls a callback with err = 'ok', the final callback of the waterfall intercepts it.

I have a similar issue & i am adding a callback wrapper(which i could avoid) just to check whether its an error or not for every waterfall that i use. Its not that clean when there are more than one waterfalls.
Can the callback function itself have another property called 'final', which people could call to go to the end.
Some thing like

function search (cond, callback) {
  async.waterfall([function (cb) {
      db.get(cond, cb);
    },
    function (res, cb) {
      if (!res || !res.length)
        return cb.final();
      //do something useful
    }
  ], callback);
}

i wont have to wrap the callback & it could be chained to rest of the program.

@caolan is there anything you dont like about @jnordberg pull request? And if so is there something I can do to get this pull request accepted?

@tax : actually I found that we can skip to the final function by passing 'error' = true like this:

async.waterfall([function (callback) {
callback(null); // <--- go to next fn
},
function (callback) {
callback(true); // <--- skip to the last fn
},
function (callback) {
callback(null); // <--- this fn will not be called
}
], callback);

@tot2ivn thanks for the tip!

If you set the error the result will be empty though.

var async = require('async');

async.waterfall( [
  function( callback ){
    console.log('one');
    callback( null );
  },

  function( callback ){
    console.log('two');
    callback( true, 'more info' );
  },

  function( callback ){
    console.log('three');
    callback( null );
  }
], function(err, result){
  console.log( err, result );
} );

// RESULT
// one
// two
// true undefined

In light of @caolan's comment upon closing pull request #85, this issue should probably be closed

Personally I find myself needing functionality like this and think it should be added to the core library.

Here is my helper workaround if anyone is interested...

exports.breakWaterfall = function(tasks, callback){
    async.waterfall(tasks, function(){
        if(arguments[0] === 'break'){
            arguments[0] = null;
        }
        callback.apply(null, arguments);
    });
}

If you need to break to the last function within a task just call the callback like this: done('break', other, arguments);.

The helper just detects the 'break' and mutates the arguments so it does not look like an error to the rest of your code.

Was this page helpful?
0 / 5 - 0 ratings