6

I have a question about the best way to chain callbacks together while passing data between them. I have an example below which works, but has the flaw that the functions have to be aware of the chain and know their position / whether to pass on a call back.

function first(data, cb1, cb2, cb3){
    console.log("first()", data);
    cb1(data,cb2, cb3);
}
function second(data, cb1, cb2) {
    console.log("second()",data);
    cb1(data, cb2);
}
function third(data, cb) {
    console.log("third()",data);
    cb(data);
}
function last(data) {
    console.log("last() ", data);
}
first("THEDATA", second, third, last);  // This work OK
first("THEDATA2", second, last);        // This works OK
first("THEDATA3", second);              // This doesn't work as second tries to pass on a callback that doesn't exit.

I can think of a number of ways around this, but they all involve in making the called functions aware of what is going on. But my problem is that the real functions I’m dealing with already exist, and I don’t want to modify them if I can avoid it. So I wondered if I was missing a trick in terms of how these could be called?

P Burke
  • 1,630
  • 2
  • 17
  • 31

5 Answers5

5

Thanks for the answers, and I agree that promises are probably the most appropriate solution, as they meet my requirements and provide a number of other advantages.

However I have also figured out how to do what I want without involving any extra modules.

To recap the specific ask was to:

  • chain together a number of functions via callbacks (this is so the first function can use a non-blocking I/O call the other functions are dependent upon),
  • while passing arguments (data) between them, and
  • without the existing functions having to be modified to be aware of their position in the callback chain.

The 'trick' I was missing was to introduce some extra anonymous callback functions to act as links between the existing functions.

// The series of functions now follow the same pattern, which is how they were before
// p1 data object and p2 is a callback, except for last().
function first(data, cb){
    console.log("first()", data);
    cb(data);
}
function second(data, cb) {
    console.log("second()",data);
    cb(data);
}
function third(data, cb) {
    console.log("third()",data);
    cb(data);
}
function last(data) {
    console.log("last() ", data);
}

// And the named functions can be called pretty much in any order without 
// the called functions knowing or caring. 

// first() + last()
first("THEDATA", function (data) { // The anonymous function is called-back, receives the data, 
    last(data);                    // and calls the next function.
});

// first() + second() + last()
first("THEDATA2", function (data) {
    second(data, function (data){
        last(data);
    }); // end second();
}); // end first();

// first() + third()! + second()! + last()
first("THEDATA3", function (data) {
    third(data, function (data){
        second(data, function (data){
            last(data);
        }); // end third();
    }); // end second();
}); // end first();
P Burke
  • 1,630
  • 2
  • 17
  • 31
1

Promises are the way to go as they provide following benefits :

  • Sequential callback chaining
  • Parallel callback chaining (kind of)
  • Exception handling
  • Easily passing around the objects

In general, a Promise performs some operation and then changes its own state to either rejected - that it failed, or resolved that its completed and we have the result. Now, each promise has method then(function(result), function(error)). This then() method is executed when the Promise is either resolved or rejected. If resolved, the first argument of the then() is executed, with the result and if Promise gets rejected 2nd argument is executed.

A typical callback chaining looks like :

example 1 :

someMethodThatReturnsPromise()
 .then(function (result) {
  // executed after the someMethodThatReturnsPromise() resolves
  return somePromise;
 }).then(function (result) {
  // executed after somePromise resolved 
 }).then(null, function (e) {
  // executed if any of the promise is rejected in the above chain
 });

How do you create a Promise at the first place ? You do it like this :

new Promise (function (resolve, reject) {
   // do some operation and when it completes
   resolve(result /*result you want to pass to "then" method*/)
   // if something wrong happens call "reject(error /*error object*/)"
});

For more details : head onto here

zeekhuge
  • 1,594
  • 1
  • 13
  • 23
0

Best practice would be to check whether the callback you are calling is provided in the args.

function first(data, cb1, cb2, cb3){
    console.log("first()", data); // return something
    if(cb1){
        cb1(data,cb2, cb3);
    }
}
function second(data, cb1, cb2) {
    console.log("second()",data); // return something
    if(cb1){
        cb1(data, cb2);
    }
}
function third(data, cb) {
    console.log("third()",data); // return something
    if(cb){
        cb(data);
    }
}
function last(data) {
    console.log("last() ", data); // return something
}
first("THEDATA", second, third, last);  // This work OK
first("THEDATA2", second, last);        // This works OK
first("THEDATA3", second);

This will work fine. Alternatively there are many more options like Promises and Async library e.tc

SAGAR RAVAL
  • 319
  • 1
  • 9
0

Maybe you want to try it this way, yes right using "Promise" has more features, but if you want something simpler, we can make it like this, yes this is without error checking, but of course we can use try / catch

function Bersambung(cb){
    this.mainfun = cb            
    this.Lanjut = function(cb){   
        var thisss = this; 
        if(cb){
            return new Bersambung(function(lanjut){
                thisss.mainfun(function(){  
                    cb(lanjut); 
                })
            })                

        } else {
            this.mainfun(function(){});
        }     

    }  
} 


//You can use it like this :

var data1 = "", data2 = "" , data3 = ""

new Bersambung(function(next){ 
    console.log("sebelum async")
    setTimeout(function(){
        data1 = "MENYIMPAN DATA : save data"
        next();
    },1000);

}).Lanjut(function(next){

    if(data1 == ""){
        return; // break the chain
    }

    console.log("sebelum async 2")
    setTimeout(function(){
        console.log("after async")
        console.log(data1);
        next();
    },1000)
}).Lanjut();
Haryanto
  • 609
  • 9
  • 17
0

Okay this might not be ideal but it works.

function foo(cb = [], args = {}) // input a chain of callback functions and argumets
{
    args.sometext += "f";
    console.log(args.sometext)
    if (cb.length == 0) 
        return; //stop if no more callback functions in chain
    else
        cb[0](cb.slice(1), args); //run next callback and remove from chain
}

function boo(cb = [], args = {}) 
{
    newArgs = {}; // if you want sperate arguments 
    newArgs.sometext = args.sometext.substring(1);
    newArgs.sometext += "b";
    console.log(newArgs.sometext)
    if (cb.length == 0)
        return;
    else
        cb[0](cb.slice(1), newArgs);
}

//execute a chain of callback functions
foo ([foo, foo, boo, boo], {sometext:""})
philip
  • 1
  • Your answer could be improved by adding more information on what the code does and how it helps the OP. – Tyler2P Jan 26 '22 at 19:30