sequencing function calls in javascript - are callbacks the only way?
Asked Answered
F

5

10

I read through various threads like this one for example.

But it really escapes me how to accomplish the following:

I have 4 functions, and want them happen one after another in sequence. Notice they are in incorrect order, to get my point across. I want the result that will output "1, 2, 3, 4'

function firstFunction(){
  // some very time consuming asynchronous code...
  console.log('1');
}
function thirdFunction(){
  // definitely dont wanna do this until secondFunction is finished
  console.log('3');
}
function secondFunction(){
  // waits for firstFunction to be completed
  console.log('2');
}
function fourthFunction(){
  // last function, not executed until the other 3 are done.
  console.log('4');
}

I tried to figure out callbacks but am getting lost :(

Isn't there some simple way to do this? Like looping through an array...

Functionary answered 5/9, 2012 at 3:28 Comment(1)
You may be interested in taking a look at promises and jQuery deferred objects.Astatine
W
16

It's a great chance to start using jQuery Deferred.

Apart from the callbacks-based solution the code is readable, flexible and highly maintainable

http://jsfiddle.net/zerkms/zJhph/

function firstFunction(){
  var d = $.Deferred();
  // some very time consuming asynchronous code...
  setTimeout(function() {
    console.log('1');
    d.resolve();
  }, 1000);
  return d.promise();
}
function thirdFunction(){
  var d = $.Deferred();
  // definitely dont wanna do this until secondFunction is finished
  setTimeout(function() {
    console.log('3');
    d.resolve();
  }, 1000);
  return d.promise();
}
function secondFunction(){
  var d = $.Deferred();
  setTimeout(function() {
    console.log('2');
    d.resolve();
  }, 1000);
  return d.promise();
}
function fourthFunction(){
  var d = $.Deferred();
  // last function, not executed until the other 3 are done.
  setTimeout(function() {
    console.log('4');
    d.resolve();
  }, 1000);
  return d.promise();
}

firstFunction().pipe(secondFunction).pipe(thirdFunction).pipe(fourthFunction);​

PS: as an example of asynchronous code I've used setTimeout. The main thing is that in the end of the asynchronous part you need to call d.resolve() to continue chaining methods.

Wyckoff answered 5/9, 2012 at 3:33 Comment(11)
you should press the outlined checkmark under the up vote/down vote if this is your selected answer.Dimitry
except it's not really my accepted answer :P cuz I dont understand it!Functionary
@tim: please read PS. Timeouts are for simulating asynchronous. Replace them with your async logic.Wyckoff
"cuz I dont understand it" --- oh, at least it's honest :-S Have you read the "further reading" link?Wyckoff
@zerkms, I'm liking the approach. So, are you saying I prepare each function as a deferred object, and when I'm done I return its .promise() in order to be able to chain these together with .pipe() ? I'd really appreciate if you can explain what you did a bit more. thanks.Functionary
@tim: the next pipe is called when previous is resolved by .resolve(). That's it.Wyckoff
@zerkms, wait a minute... wouldn't the return statement be exectuted before the timeout is finished?Functionary
@tim: well, I wasn't precise enough: the pipe callback is called only after previous pipe was resolved. So the return statement is executed immediately, and it has a jquery promise object, but what is pipelined will be called only after promise is resolved.Wyckoff
how do you do this if the say, second function has a parameter? firstFunction().pipe(secondFunction(param)).pipe(thirdFunction).pipe(fourthFunction);​ ?Peeler
@NilsSens pipe(() => secondFunction(param)). So you pass another function that calls a secondFunction with an argument. Because otherwise you immediately call a function.Wyckoff
I found this answer helpful. Just a comment: $.pipe is deprecated from jQuery 1.8 in favor of $.then.Goose
D
1

The idea is you'd do something like the following so that once the first function was done running, it'd know what to run as opposed to you having to figure it out on your own outside the function:

function firstFunction(callback){
  // some very time consuming asynchronous code...
  console.log('1');

  return callback(function(){
    alert("Second function finished.");
    return true;
  });
}
function secondFunction(callback){
  // waits for firstFunction to be completed
  console.log('2');

  return callback();
}

firstFunction(secondFunction);

Also look up .apply() and .call().

Dimitry answered 5/9, 2012 at 3:32 Comment(3)
Shouldn't that be "First function finished" or am I being thick?Effluence
It's kind of both, but it mainly is the second function that actually finishes because it's passing the function on to the second function. In other words, that alert will alert when the second function calls the callback, not when the first function calls the second function.Dimitry
Thats why I cant stand this callback stuff, its really hard to track the goings-on.Functionary
F
1

If I'm using callbacks, my working solution now looks like this:

    one(two);
    function one(callb){
        console.log('1');
        callb(three);
    }
    function four(){
        console.log('4');
    }
    function two(callb){
        console.log('2');
        callb(four);
    }
    function three(callb){
        console.log('3');
        callb();
    }

I find that hideous. How am I supposed to keep track of this stuff if there is more than 2-3 sequences? Shudder...

Functionary answered 5/9, 2012 at 3:43 Comment(2)
"How am I supposed to keep track of this stuff if there is more than 2-3 sequences?" -- isn't deferreds a panacea?Wyckoff
after reading up on it, it seems to be the "remedy of my ills" (I had to look up panacea...)Functionary
F
0

It's been a while and I noticed something about deferreds in jquery documentation, specifically the when core API function.

$.when( $.ajax("test.aspx") ).then(function(ajaxArgs){ 
     alert(ajaxArgs[1]); /* ajaxArgs is [ "success", statusText, jqXHR ] */
});

Code sample taken from http://jqapi.com/#p=jQuery.when

Functionary answered 18/10, 2012 at 20:47 Comment(0)
A
0

I have played with the Promise, Sequence, Exception, Callback to understand how it works and finally made this code.

Call functions with callback and send result as parameter to another function in sequence and have a catch errors.

function firstFunction(par) {
    return new Promise(function (resolve, reject) {
        console.log("start " + par);
        setTimeout(function (par) {
            console.log(par);
            resolve(par + 1);
        }, 1000, par);
    });
}
function secondFunction(par) {
    return new Promise(function (resolve, reject) {
        console.log("start " + par);
        setTimeout(function (par) {
            console.log(par);
            try{
                throw "Let's make an error...";
            }
            catch(err)
            {
                reject(err);
            }
            resolve(par + 1);
        }, 1000, par);
    })
}
function thirdFunction(par) {
    return new Promise(function (resolve, reject) {
        console.log("start " + par);
        setTimeout(function (par) {
            console.log(par);
            resolve(par + 1);
        }, 1000, par);
    });
}

function CatchError(error) {
    console.log("Exception: " + error);
}

//Break all chain in second function
function ChainBrake() {
    firstFunction(1)
    .then(secondFunction)
    .then(thirdFunction)
    .catch(CatchError);    
}

//Log error and continue executing chain
function ChainContinue() {
    firstFunction(1)
    .catch(CatchError)
    .then(secondFunction)
    .catch(CatchError)
    .then(thirdFunction)
    .catch(CatchError);
}
Adagio answered 3/10, 2016 at 18:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.