21

My specific problem is that I need to execute a (potentially) large number of Javascript functions to prepare something like a batch file (each function call adds some information to the same batch file) and then, after all those calls are completed, execute a final function to send the batch file (say, send it as an HTML response). I'm looking for a general Javascript programming pattern for this.

Generalize problem: Given the Javascript functions funcA(), funcB(), and funcC(), I would to figure out the best way to order execution so that funcC is only executed after after funcA and funcB have executed. I know that I could use nested callback functions like this:

funcA = function() {
    //Does funcA stuff
    funcB();
}
funcB = function() {
    //Does funcB stuff
    funcC();
}

funcA();

I could even make this pattern a little more general by passing in callback parameters, however, this solution becomes quite verbose.

I am also familiar with Javascript function chaining where a solution might look like:

myObj = {}
myObj.answer = ""
myObj.funcA = function() {
    //Do some work on this.answer
    return this;
}
myObj.funcB = function() {
    //Do some more work on this.answer
    return this;
}
myObj.funcC = function() {
    //Use the value of this.answer now that funcA and funcB have made their modifications
    return this;
}
myObj.funcA().funcB().funcC();

While this solution seems a little cleaner to me, as you add more steps to the computation, the chain of function executions grows longer and longer.

For my specific problem, the order in which funcA, funcB, etc. are executed DOES NOT matter. So in my solutions above, I am technically doing more work than is required because I am placing all the functions in a serial ordering. All that matters to me is that funcC (some function for sending the result or firing off a request) is only called after funcA and funcB have ALL completed execution. Ideally, funcC could somehow listen for all the intermediate function calls to complete and THEN would execute? I hoping to learn a general Javascript pattern to solve such a problem.

Thanks for your help.

Another Idea: Maybe pass a shared object to funcA and funcB and when they complete execution mark the shared object like sharedThing.funcA = "complete" or sharedThing.funcB = "complete" and then somehow? have funcC execute when the shared object reaches a state where all fields are marked complete. I'm not sure how exactly you could make funcC wait for this.

Edit: I should note that I'm using server-side Javascript (Node.js) and I would like to learn a pattern to solve it just using plain old Javascript (without the use of jQuery or other libraries). Surely this problem is general enough that there is a clean pure-Javascript solution?

5
  • Build an array of references to your functions in the order you want to execute them, and then pass the array to another function that just iterates through the array calling functions. Make sure the one you want to be last is last. Commented Jun 30, 2012 at 22:02
  • 1
    Or just write the code in the order you want it to be executed; it's not clear to me what the problem really is. Sequential imperative coding always makes you commit to an order of execution of program statements; what's special about this situation? Commented Jun 30, 2012 at 22:03
  • 1
    That's pretty smart Pointy, I never though about doing like that. What about if your function has a delay to get started? Will it still wait to be finished? Commented Jun 30, 2012 at 22:04
  • 2
    It may be overkill, but jQuery has a deferred feature. Basically it allows you to do a $.when(A, B).then(C) like syntax. Commented Jun 30, 2012 at 22:04
  • As Pointy said, what is the real problem here? Are any of these functions asynchronous? If not, then javascript just executes things sequentially as you've coded them. Commented Jun 30, 2012 at 23:34

7 Answers 7

9

If you want to keep it simple, you can use a counter-based callbacks system. Here's a draft of a system that allows when(A, B).then(C) syntax. (when/then is actually just sugar, but then again the whole system arguably is.)

var when = function() {
  var args = arguments;  // the functions to execute first
  return {
    then: function(done) {
      var counter = 0;
      for(var i = 0; i < args.length; i++) {
        // call each function with a function to call on done
        args[i](function() {
          counter++;
          if(counter === args.length) {  // all functions have notified they're done
            done();
          }
        });
      }
    }
  };
};

Usage:

when(
  function(done) {
    // do things
    done();
  },
  function(done) {
    // do things
    setTimeout(done, 1000);
  },
  ...
).then(function() {
  // all are done
});
Sign up to request clarification or add additional context in comments.

Comments

3

If you don't use any asynchronous functions and your script doesn't break the order of execution, then the most simple solution is, as stated by Pointy and others:

funcA(); 
funcB();
funcC();

However, since you're using node.js, I believe you're going to use asynchronous functions and want to execute funcC after a async IO request has finished, so you have to use some kind of counting mechanisms, for example:

var call_after_completion = function(callback){
    this._callback = callback;
    this._args = [].slice.call(arguments,1);
    this._queue = {};
    this._count = 0;
    this._run = false;
}

call_after_completion.prototype.add_condition = function(str){
    if(this._queue[str] !== undefined)
        throw new TypeError("Identifier '"+str+"' used twice");
    else if(typeof str !== "String" && str.toString === undefined)
        throw new TypeError("Identifier has to be a string or needs a toString method");

    this._queue[str] = 1;
    this._count++;
    return str;
}

call_after_completion.prototype.remove_condition = function(str){
    if(this._queue[str] === undefined){
        console.log("Removal of condition '"+str+"' has no effect");
        return;
    }
    else if(typeof str !== "String" && str.toString === undefined)
        throw new TypeError("Identifier has to be a string or needs a toString method");

    delete this._queue[str];

    if(--this._count === 0 && this._run === false){
        this._run = true;
        this._callback.apply(null,this._args);
    }
}

You can simplify this object by ignoring the identifier str and just increasing/decreasing this._count, however this system could be useful for debugging.

In order to use call_after_completion you simply create a new call_after_completion with your desired function func as argument and add_conditions. func will only be called if all conditions have been removed.

Example:

var foo = function(){console.log("foo");}
var bar = new call_after_completion(foo);
var i;

bar.add_condition("foo:3-Second-Timer");
bar.add_condition("foo:additional function");
bar.add_condition("foo:for-loop-finished");

function additional_stuff(cond){
    console.log("additional things");
    cond.remove_condition("foo:additional function");
}

for(i = 0; i < 1000; ++i){

}
console.log("for loop finished");
bar.remove_condition("foo:for-loop-finished");
additional_stuff(bar);

setTimeout(function(){
    console.log("3 second timeout");
    bar.remove_condition("foo:3-Second-Timer");
},3000);

JSFiddle Demo

Comments

2

If you don't want to use any helper libraries, than you need to write some helper yourself, there's no simple one line solution for this.

If you'd like to end with something that looks as readable as it would in synchronous case, try some deferred/promise concept implementation (it's still plain JavaScript), e.g. using deferred package you may end up with something as simple as:

// Invoke one after another:
funcA()(funcB)(funcC);

// Invoke funcA and funcB simultaneously and afterwards funcC:
funcA()(funcB())(funcC);

// If want result of both funcA and funcB to be passed to funcC:
deferred(funcA(), funcB())(funcC);

Comments

0

Have a look into jQuery's deferred objects. This provides a sophisticated means of controlling what happens when in an asynchronous environment.

The obvious use-case for this is AJAX, but it is not restricted to this.

Resources:

1 Comment

This sounds interesting but 2 of the 3 links you provide are broken.
0

I was looking for the same kind of pattern. I am using APIs that interrogate multiple remote data sources. The APIs each require that I pass a callback function to them. This means that I cannot just fire off a set of my own functions and wait for them to return. Instead I need a solution that works with a set of callbacks that might be called in any order depending on how responsive the different data sources are.

I came up with the following solution. JS is way down the list of languages that I am most familiar with, so this may not be a very JS idiom.

function getCallbackCreator( number_of_data_callbacks, final_callback ) {

    var all_data = {}

    return function ( data_key ) {

        return function( data_value ) {
            all_data[data_key] = data_value;

            if ( Object.keys(all_data).length == number_of_data_callbacks ) {
                final_callback( all_data );
            }
        }
    }
}

var getCallback = getCallbackCreator( 2, inflatePage );

myGoogleDataFetcher( getCallback( 'google' ) );
myCartoDataFetcher( getCallback( 'cartodb' ) );

Edit: The question was tagged with node.js but the OP said, "I'm looking for a general Javascript programming pattern for this," so I have posted this even though I am not using node.

Comments

0

Nowadays, one can do something like this:

Let's say we have both funcA, funcB and funcC:

If one's want funcA and funcB results to be passed to funcC:

var promiseA = new Promise((resolve, reject) => {
  resolve(await funcA());
});
var promiseB = new Promise((resolve, reject) => {
  resolve(await funcB());
});
var promise = Promise.all([ promiseA, promiseB ]).then(results => {
  // results = [result from funcA, result from funcB]
  return funcC(results);
});

If one's want funcA, then funcB and then funcC:

var promise = (
  new Promise(async resolve => resolve( await funcA() ))
).then(result_a => funcB(result_a)).then(result_b => funcC(result_b));

And finally:

promise.then(result_c => console.log('done.'));

Comments

-1
how about:
funcC(funcB(funcA)));

I think the questions is because some of functions run longer and there might be a situation when we run funcC when funcA or funcB did not fininsh executing.

2 Comments

In @Aleksander Kogut answer more clear vision but from your approach, it can be like: result = funcA(); result = funcB(result); funcC(result); because I would pass the chain of results.
just corrected the previos code. For me now works perfect. I even use more nested functions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.