Ozk
Ozk

Reputation: 191

node.js generic function execution using function.prototype.apply

I have a bunch of action methods such as:

function doA(x, y, callback){

  .. some additional logic here ...

  do_something_async(x, y, function(result) {
    callback(result);
  });
}
function doB(x, y, z, callback){

  .. some additional logic here ...

  do_something_async2(x, y, z, function(result) {
    callback(result);
  });
}

And I'd like to create a generic execution function such as:

function doCommand(fn) {
  // Execute the fn function here with the arguments
  // Do something with the result to the callback from the action function
  // (which may require running the action function again...)
}

The function doCommand will receive as a first argument the name of the action command and the rest of the arguments will be as required by the action command. In other words: doCommand(doA, 5, 10, cbfn); will call doA with the relevant arguments. And doCommand(doB, 1, 2, 3, cbfn) will call doB with the relevant arguments.

So far, my doCommand function looks like this:

function doCommand(fn) {
    fn.apply(null, arguments);
}

However, I have no idea how to capture inside doCommand the value of result after the async function execution because based on this value I may need to do something in addition, for example, run the action function again. Plus, this will require a change the signature of every action command to disregard the first null argument created by the apply function.

I'm pretty sure there is a smarter way to do this. Would appreciate your help in finding out how.

Thanks!

EDIT: A typical action function would look like this:

function doA(arg1, arg2, cb) { 
    ... some logic using arguments ... 
    asyncmodule.once('event', function(event) { 
        ... do something ... 
        callback(event.result); 
    }); 
    asyncmodule.send('A command', [list of specific arguments]); 
} 

The caller to the action function needs to do something with the result, however for specified failure results, there needs to be either a repeat or changing of arguments and retry.

Upvotes: 0

Views: 1694

Answers (2)

Aaron Dufour
Aaron Dufour

Reputation: 17505

There's a pretty strong convention of the callback being the final argument to a function, so I'll assume you're using that. We'll start by fixing up the arguments and injecting our own callback, and then we can call the function and be on our way.

function doCommand(fn) {
    var args = Array.prototype.slice.call(arguments); // We want a real array
    args = args.slice(1); // Otherwise it contains fn, which isn't right
    var finalCallback = args[args.length - 1]; // We'll need this at the end
    args = args.slice(0, -1); // But we don't need it now
    fn.apply(null, args.concat( function() { // This looks weird, but it allows us
                                             // to keep the callback inline with
                                             // the function call
        // Here you can operate on the results
    }));
}

Note that concat is non-destructive, so you can call fn like that as many times as you like with different callbacks. Don't forget to eventually call finalCallback!

As Bergi points out, you can combine all of the slices - I just pulled them apart so I could comment them better:

var args = Array.prototype.slice.call(arguments, 1, -1)

And you'd have to pull the finalCallback out of arguments instead of args, of course.

Upvotes: 2

JLRishe
JLRishe

Reputation: 101672

Unless you have a practical way of identifying which argument is a callback function, I don't see a good way to do this.

However, this is yet another place where using promises instead of callbacks would make an asynchronous task simpler. If your async functions used promises instead of callbacks, then you could do this:

function doA(x, y){
  return do_something_with_promises(x, y);
}
function doB(x, y, z){
  return do_something_with_promises2(x, y, z);
}

function doCommand(fn) {
    var args = Array.prototype.slice.call(arguments, 1);

    return fn.apply(null, args)
    .then(function (result) {
        if (result === "run me again!") {
            return fn.apply(null, args);
        }
    });
}

doCommand(doA, "Hey!", "Hey!")
.then(function (result) {
    // ...
});

Upvotes: 0

Related Questions