How to improve the following callback pattern? - javascript

I have the following pattern which strings together function1, 2 and 3 through their callbacks.
Assume that function1, 2 and 3 can take up to 1 second to complete. I would like to know other "better" ways of doing the same so that it doesn't turn into a monster when more callback functions are nested.
function1(function(cbData1){
if(cbData1){
function2(cbData1, function(cbData2){
if(cbData2){
function3(cbData2, function(cbData3){
// success
}
} else {
// failed for reason#2
}
});
} else {
//failed for reason#1
}
});
//example function
function function2(data, callback) {
// do dirty things
callback(newData);
}

If I understand you correctly you need to organize the callbacks in a chain. Look at Chain of Responsibility pattern.
So you will create an object containing the function to execute and callback function to execute if needed.

The last time I played with really nasty callbacks, I ended up doing something like this:
// Typed on the fly, be kind
var callbackList = []; // A list of functions to call in order.
function doNextCallback() {
if (callbackList.length) {
var f = callbackList.shift(); // Get the next callback function
window.setTimeout(f); // Give breathing space.
}
}
// Set up our callbacks
callbackList.push(f1);
callbackList.push(f2);
callbackList.push(f3);
// Start it happening.
doNextCallback();
function f1() {
console.log("Busy busy");
doNextCallback();
}
function f2() {
console.log("Busy busy");
doNextCallback();
}
function f3() {
console.log("Busy busy");
doNextCallback();
}
I had it all wrapped up in a nice object, but you get the idea.
This also made it very easy to re-arrange callbacks or to call just two of them in a big loop for testing purposes.

Related

How to wait for callback while iterating an array

I am working with a transnational framework within Javascript. So I need to wait for the previous query to finish before I move on. For example...
// Explicit this won't work because the length is not static
var i = [1,2,3]
doSomething(i[0], function(){
doSomething(i[1], function(){
doSomething(i[2], function(){
commitTransaction()
}
})
})
From this example I can't figure out a way to do this dynamically. It feels like a queue/recursion problem but I can't seem to crack it.
Does anyone else have an idea? I can also wrap in promises so that is an option as well, although that seems less synchronous.
Use async.eachSeries. So your code would translate to:
var transaction = {...};
async.eachSeries([1, 2, 3], function(value, callback) {
doSomething(value, transaction, callback);
}, function(err) {
if(err) throw err; // if there is any error in doSomething
commitTransaction(transaction);
});
jsFiddle Demo
I would suggest making a queue to do this. It would take the array, the generic callback function and a final function to callback with. Basically, the best way to accomplish this is to allow your functions to expect to have values injected.
The core assumption is that it is understood the caller will allow their callback function to have the current value and next callback function injected. That basically means we will end up with a function I have named queueAll which looks like this
function queueAll(arr,cbIteration,final){
var queue = [function(){ cbIteration(arr[arr.length-1],final) }];
for(var i = arr.length-2; i > 0; i--){
(function(next,i){
queue.unshift(function(){ cbIteration(arr[i],next) });
})(queue[0],i)
}
cbIteration(arr[0],queue[0]);
}
It takes the final call, places it in the queue, and then iterates, placing subsequent callback functions in the queue with the current value closed over, as well as closing over the front of the queue which at that point is the next call back. It is fairly simple to use. Pass it an array, a callback which expects values to be injected, and a final function.
In your case it would look like
queueAll(i,function(item,next){
doSomething(item,next);
},function(){
commitTransaction();
});
Stack Snippet Demo
//## <helper queue>
function queueAll(arr,cbIteration,final){
var queue = [function(){ cbIteration(arr[arr.length-1],final) }];
for(var i = arr.length-2; i > 0; i--){
(function(next,i){
queue.unshift(function(){ cbIteration(arr[i],next) });
})(queue[0],i)
}
cbIteration(arr[0],queue[0]);
}
//## </helper queue>
//## <user defined functions>
function doSomething(val,callback){
setTimeout(function(){
console.log(val);
callback();
},val*10);
}
function commitTransaction(){
console.log("commit");
}
//## </user defined functions>
//## <actual use>
var arr = [10,20,30];
queueAll(arr,function(item,next){
doSomething(item,next);
},function(){
commitTransaction();
});
//## </actual use>
Actually, I think promises are exactly what you're looking for. But for a traditional callback approach, consider the following:
var state = false,
doSomething = function (value, callback) {
/* do stuff with value */
if (!state)
doSomething(newValue, callback);
else
callback();
};

Nesting callbacks for more than two tasks

It is possible to determine the order of TWO tasks using callbacks, as shown below.
a(b);
function a(callback) {
// do something
callback();
}
function b() {
// do next
}
See Fiddle
First do a(), then do b().
I would like to concatenate more than two tasks.
As I´m dealing with quite big functions, I´m looking for something like that:
a(b(c));
First do a(), then do b(), then do c().
However I'm not successful with this. See Fiddle
Is there an easy way to do so, maybe without needing Promises?
You're calling b immediately, not passing a callback to a. You'll need to use a function expression:
a(function(aResult) {
b(c);
});
Of course, you can avoid these by returning closures from all your functions:
function a(callback) {
return function(args) {
// do something
if (callback) callback(res);
};
}
function b(callback) {
return function(aResult) {
// do next
if (callback) callback(res);
};
}
function c(callback) {
return function(bResult) {
// do next
if (callback) callback(res);
};
}
which you would call like this:
a(b(c())();
(this is known as pure continuation passing style)

Underscore js times series

I'm using UnderscoreJs with nodejs and have a need for the _.times() method. times() will invoke a function X number of times
This works as expected, however I need to iterate in a series, instead of in parallel which this appears to be doing.
Any idea if there's a way to use this in series w/ callback methods?
Given something like this:
function f() {
some_async_call({ callback: function(err, results) {...})
}
_(3).times(f);
Then the three f calls will happen in series but the some_async_call calls won't necessarily happen in series because they're asynchronous.
If you want to force your calls to run in series then you need to use the callback on the async call to launch the next one in the series:
function f(times, step) {
step = step || 0;
some_async_call({
callback: function(err, results) {
// Do something with `err` and `results`...
if(step < times)
f(times, step + 1);
}
});
}
f(3);
That approach will execute the three some_async_calls in series but, alas, the initial f(3) will return immediately. One solution to that problem is, of course, another callback:
function f(from_n, upto, and_finally) {
some_async_call({
callback: function(err, results) {
// Do something with `err` and `results`...
if(from_n < upto)
f(from_n + 1, upto, and_finally);
else
and_finally();
}
});
}
f(0, 3, function() { console.log('all done') });
Where does _.times in with all this? No where really. _.times is just a for loop:
_.times = function(n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
_.times exists for completeness and to allow you to add for loop when using _.chain. You could probably shoe-horn it in if you really wanted to but you would be making a big ugly mess instead of simplifying your code.
You could use 250R's async idea but you'd have to build an array of three functions but _.range and _.map would be more appropriate for that than _.times:
// Untested off the top of my head code...
function f(callback) {
some_async_call({
callback: function(err, results) {
// Deal with `err` and `results`...
callback();
}
});
}
var three_fs = _(3).range().map(function() { return f });
async.series(three_fs);
But you still have to modify f to have a callback function and if you're always calling f three times then:
async.series([f, f, f]);
might be better than dynamically building the array with _.range and _.map.
The real lesson here is that once you get into asynchronous function calls, you end up implementing all your logic as callbacks calling callbacks calling callbacks, callbacks all the way down.
This async library might get you started
https://github.com/caolan/async#series
Or if you want to do it yourself, the idea is to do recursive calls after each function callback is called, here's the source code https://github.com/caolan/async/blob/master/lib/async.js#L101

how to avoid callback chains?

I need a bunch of functions to be called in strict order. It's also very important that the next function waits until the previous one has finished.
Right now I'm using chained callbacks:
callMe1(function(){
callMe2(function(){
callMe3(function(){
callMeFinal();
});
});
});
This works but seems to be a little ugly.
Any suggestions for a different approach?
If you use jQuery, then you can use queue to chain the functions.
$(document)
.queue(callMe1)
.queue(callMe2);
where callMeX should be of form:
function callMeX(next) {
// do stuff
next();
}
You can implement a "stack" system:
var calls = [];
function executeNext(next) {
if(calls.length == 0) return;
var fnc = calls.pop();
fnc();
if(next) {
executeNext(true);
}
}
/*To call method chain synchronously*/
calls.push(callMe3);
calls.push(callMe2);
calls.push(callMe1);
executeNext(true);
/*To call method chain asynchronously*/
calls.push(callMe3);
calls.push(function(){
callMe2();
executeNext(false);
});
calls.push(function(){
callMe1();
executeNext(false);
});
Not sure if this would help you, but there is a great article on using deferreds in jQuery 1.5. It might clean up your chain a bit...
Also, my answer on Can somebody explain jQuery queue to me has some examples of using a queue for ensuring sequential calls.
You might want to pass parameters to the functions, I do not believe you can at the time of this writing. However...
function callMe1(next) {
console.log(this.x);
console.log("arguments=");
console.log(arguments);
console.log("/funct 1");
this.x++;
next();
}
function callMe2(next) {
console.log(this.x);
console.log("arguments=");
console.log(arguments);
console.log("/funct 2");
this.x++;
next();
}
function callMe3(next) {
console.log(this.x);
console.log("arguments=");
console.log(arguments);
console.log("/funct 3");
this.x++;
next();
}
var someObject = ({x:1});
$(someObject).queue(callMe1).queue(callMe2).queue(callMe3);
Wrapping your functions, arguments intact, with an anonymous function that plays along with .queue works too.
Passing Arguments in Jquery.Queue()
var logger = function(str, callback){
console.log(str);
//anything can go in here, but here's a timer to demonstrate async
window.setTimeout(callback,1000)
}
$(document)
.queue(function(next){logger("Hi",next);})
.queue(function(next){logger("there,",next);})
.queue(function(next){logger("home",next);})
.queue(function(next){logger("planet!",next);});
Example on JSFiddle: http://jsfiddle.net/rS4y4/

Flow control in JavaScript

Is it possible to write this flow control in JavaScript?
MyLib.get = function() { /* do something */ next(); };
MyLib.save = function() { /* do something */ next(); };
MyLib.alert = function() { /* do something */ next(); };
MyLib.flow([
MyLib.get(),
MyLib.save(),
MyLib.alert()
], function() {
// all functions were executed
});
Yes, but two things to know:
You should build the array from references to your functions. That means you leave off the (), because you just want to pass through the reference, not the result of calling the function!
You're going to have to deal with the fact that getting references to functions from properties of an object will not "remember" the relationship to that object. Thus, the "flow" code would have no way to know how to invoke the functions with "MyLib" as the this context reference. If that's important, you'll want to create functions that run your "member" functions in the right context.
To run the functions in the right context, you can cobble together something like the "bind" function supplied by the Prototype framework (and also, among many others, Functional.js), or $.proxy() from jQuery. It's not that hard and would probably look like this:
function bindToObject(obj, func) {
return function() {
func.apply(obj, arguments);
}
}
then you'd use it like this:
MyLib.flow([
bindToObject(MyLib, MyLib.get),
bindToObject(MyLib, MyLib.save),
bindToObject(MyLib, MyLib.alert)
]);
If you need for parameters to be passed in, you could modify "bindToObject":
function bindToObject(obj, func) {
var preSuppliedArgs = Array.prototype.slice.call(arguments, 2);
return function() {
func.apply(obj, preSuppliedArgs.splice(arguments));
}
}
That assumes you'd want additional arguments passed when the "bound" function is called to be tacked on to the end of the argument list. In your case, I doubt you'd want to do that, so you could leave off that "splice()" call.
This looks like continuation-passing style. However, normally in that style, each function takes a next function as an argument, like this:
MyLib.get = function(next) { /* do something */ next(); };
MyLib.save = function(next) { /* do something */ next(); };
MyLib.alert = function(next) { /* do something */ next(); };
and as Pointy notes, you would normally pass the functions themselves, without having already called them:
MyLib.flow([
MyLib.get,
MyLib.save,
MyLib.alert
], function() {
// all functions were executed
});
With those changes, the code below might work.
Now, it's by no means obvious to the uninitiated exactly how continuation-passing style works just by looking at the code. I don't think I can make it clear in a single answer. But I'll try.
In this style, even a do-nothing function would not be totally empty, but would have to call next:
MyLib.do_nothing = function(next) { /* don't do something */ next(); };
One important building block is the ability to take two functions written in this style and chain them together:
// This function takes two CPS functions, f1 and f2, as arguments.
// It returns a single CPS function that calls f1, then f2, then next().
MyLib._compose2 = function (f1, f2) {
return function(next) {
return f1(function () { return f2(next); });
};
};
This is the hardest bit to understand, I think.
Once you have that, you can use it to glue together any number of functions:
MyLib._composeAll = function (arr) { // Easy!
var result = do_nothing; // Start with the "empty" function,
for (var i = 0; i < arr.length; i++) // and one by one,
result = MyLib._compose2(result, arr[i]); // add each element of arr.
return result;
};
And once you have that, flow is not too hard to write:
MyLib.flow = function(items, next) {
var f = MyLib._composeAll(items);
f(next);
};
Fixing all the bugs in that code is left as an exercise. ;)

Categories

Resources