Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reduced nesting with async.waterfall but added clutter

I am trying to reduce the nesting of async calls (node + socket.io) by using async.waterfall and I ended up having to append parameters down the waterfall because they are needed later. This code might explain better:

// Original version:

 socket event: turn action
  socket.on('turn action', function(gameId, turnAction, clientFn) {
    socket.get('corp', function(err, corp) {
      gameProvider.processTurnAction(gameId, corp.id, turnAction, function(err, msg, game) {
        clientFn(msg, game);
      });
    });
  });

// async.js version

  async.waterfall([
    function(callback) {
      socket.on('turn action', function(gameId, turnAction, clientFn) {        
        callback(null, gameId, turnAction, clientFn);
      });
    },
    function(gameId, turnAction, clientFn, callback) {
      socket.get('corp', function(err, corp) {
        callback(null, gameId, turnAction, clientFn, corp);
      });
    },
    function(gameId, turnAction, clientFn, corp, callback) {
      gameProvider.processTurnAction(gameId, corp.id, turnAction, function(err, msg, game) {
        clientFn(msg,game);
      });
    }
  ]);

The goal was readability but I find the redundant param passing adds clutter. I know that I can declare variables before the call to async.waterfall and store the params as needed for later use in the chain but that doesn't help with readability.

Is there a way to make this more elegant?

like image 525
wtjones Avatar asked Sep 02 '12 21:09

wtjones


1 Answers

I'm curious about the first function in your waterfall that sets up the turn action handler. Since it just specifies an event handler, it's technically synchronous (even though the handler itself will be called asynchronously). I'd probably refactor it thusly:

socket.on('turn action', function(gameId, turnAction, clientFn) {
  async.waterfall([
    function(callback) { socket.get('corp', callback); },
    function(corp, callback) {
      gameProvider.processTurnAction(gameId, corp.id, turnAction, callback);
    }
  ], function(err, msg, game) {
    // err will be set if either of the two `callback`s were called with
    // an error as the first parameter
    clientFn(msg, game);
  });
}

This has the added benefit of passing any error parameters into the final callback, so you can handle them as necessary (e.g. call clientFn with a parameter specifying an error).

like image 180
Michelle Tilley Avatar answered Nov 15 '22 09:11

Michelle Tilley