Skip to content

Instantly share code, notes, and snippets.

@shimondoodkin
Last active December 18, 2015 03:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save shimondoodkin/5718436 to your computer and use it in GitHub Desktop.
Save shimondoodkin/5718436 to your computer and use it in GitHub Desktop.
the basics of programming with callbacks:
structure:
function simple1(cb) // the last argument is callback function
{
if(cb)return cb();// the return is required to prevent continuation of the function.
// the if(cb) is optional and it is to enable to omit the callback in rare cases.
// it is usually useful in api functions.
}
//please note: callback is expected to be called only once
to make a function asynchronous capble:
add cb as last argument.
put at the end: if(cb)return cb();
the callback function:
simple form:
var mycallback=function(data){}
standard form:
var mycallback=function(error,data){}
// when there is no error. error usually contains null or maybe undefined
// usage with null as error
function simple2(a,b,cb)
{
if(b==0) return cb(new Error('error description'));
var data=a/b;
if(cb)return cb(null,data);
}
usage, noticing the error:
//
simple2(1, 3, function(err,data) {
if(error){
throw error;
// or do something else like
console.log(error.stack)
return;
/// or call the callback immidietly passing the error.
return cb(error);
}
console.log('data=',data)
});
usage,passing the same callback on to next function:
function simple3(a,b,cb)
{
a++;
return simple2(a,b,cb);
}
suppose you want to add or reorder the arguments.
using anonymous function to pass arguments as closure to a function witch is a callback:
function simple3_version2(a,b,cb)
{
var preva=a;
a++;
return simple2(a,b,function(err,data){ if(cb) cb(err,data,preva)});
}
do not call callbacks twice:
please pay attention there is a common error, "A Spcecial Case" of EventEmitter.
events in EventEmitter are emited multiple times. not Once as expected by some inattentive programmers.
this is the most common case of errors. if you call them twice it creates a swirl of accelerating callback calls.
callbacks are expected to be called only once. please do not call the same callback twice.
be careful, and have fun with callbacks.
example: // disableing callback after it has been called:
function Connection(cb) //for example a constructor
{
this.callback=cb// have callback
var that=this;
this.on('error',function(err){
if(that.callback)that.callback(err); // check if callback avalible, and only than call it.
that.callback=null;//disable the callback somehow so it won't be called twice
})
this.on('connected',function(){
var err=null;
var conn=that;
if(that.callback)that.callback(err,conn); // check if callback avalible, and only than call it.
that.callback=null;//disable the callback in all places after calling it
})
}
Connection.prototype = Object.create(require('events').EventEmitter.prototype); //inherit event emmiter
//test for error
var conn=new Connection(function(err,connection){ if(err) {console.log('incallback',err.stack) }else {console.log('connected');} });
conn.on('error',function(err){console.log('onerror',err.stack)})// must be specified as fallback for onerror it it is not handled by the callback
// note that by node.js specification a special case of EventEmitter is that on 'error' event throws its error
// if on error is emited and no error handler is specified.
conn.emit('error',new Error('test error1'));
conn.emit('error',new Error('test error2'));
//test for success than and than for an error
var conn=new Connection(function(err,connection){ if(err) {console.log('incallback',err.stack) }else {console.log('connected');} });
conn.on('error',function(err){console.log('onerror',err.stack)})
conn.emit('connected'); // or try this one
conn.emit('error',new Error('test error3'));
//use the name cb all over the place and it just works:
var async=require('async');
var items=[1,2,3]
function calc(a,cb) {
var err=null;
return cb(err, a*a)
}
function simple4(items,cb)
{
async.mapLimit(items,4,function(item,cb)
{
calc(item, cb) // pass the inner cb of for each
},
function(err,results){
console.log('done all');
cb(results); // function cb
});
}
// another example:
function simple4_b(items,cb)
{
var results={};
async.parallel(
[
function(cb) {
console.log('1');
cb() // inner cb
}
,
function(cb) {
console.log('1');
cb() // inner cb
}
]
,
function(){
console.log('done all');
cb(); // function cb
}
);
}
// a shortcut cb
function simple5(items,cb1)
{
var results={}
function cb(){cb1(results)} // sometimes people call the function next instead of cb
async.eachLimit(Object.keys(items),4,function(k,cb)
{
var item=items[k]
calc(item,function(err,r){results[k]=r;cb(err)})
},function(err){
console.log('done all');
cb();
});
}
//while loop:
function simple6(times,cb)
{
function loop()
{
times--;
if(times==5)return loop();// use return iterator() as continue
// do something here
if(times>0)return loop(); else return cb()// use return cb() as break
}
if(times>0)loop(); else cb()
}
// retry
//from this:
function simple7(options,cb)
{
do_async_request(options,function(err,data){
if(err){
console.log('simple7 - error, faild',err.stack,options);
return;
}
//do work with data
})
}
//to this:
// 1. wrap everything inside with a local function and call this function.
// 2. add some retry code
function simple7_with_retry(options,cb)
{
var retries=0;
function retry()
{
do_async_request(options,function(err,data){
if(err){
if(retries<7)
{
retries++;
console.log('simple7 - error, retrying',err.stack,options);
setTimeout(retry,3000)
return;
}
else {
console.log('simple7 - error, faild',err.stack,options);
return;
}
}
//do work with data
})
}
retry()
}
//use the name cb all over the place and it just works:
var async=require('async');
var items=[1,2,3]
function calc(a,cb) {
cb(a*a)
}
// you can iterate keys if you like to:
// use iterate keys to iterate parallel objects parallely sinchroniously or use the key for something else
var async=require('async');
function calc(a,cb) {
cb(a*a)
}
var item_names=["a","b","c"]
var item_value=[1,2,3]
function simple4(cb)
{
var results={};
var items_keys=Object.keys(item_names)
async.eachLimit(items_keys,4,function(k,cb)
{
var name=item_names[k]
var value=item_value[k]
result[k]=name+' '+calc(value,cb)
},function(){
console.log('done all');
cb(results);
});
}
// with callbacks you have a problem of calling a calkback twice. then the next function will be called twice and if it loops back to first function or response.end then it will call it twice and it will loops in parallel like a snowball.
//it is possible to prevent it.
//sometimes it is an external event library which emits twice and yo don't want to mess with it so you simply can prevent double calling.
// unintended an unexpected double callback calling is problematic and can cause unexpected infinite loops so some times it is good to debug it and prevent double calling in your code.
function simple8(cb)
{
var cb1=cb;
cb=function() {
if(cb1) { var cb2=cb1; cb1=false; return cb2.apply(this,arguments); }
//else console.log(new Error('cb called twice').stack);
}
cb();
cb();
}
//it is posible to make a generator function for that.
function cbguard(cb,printerr){
var cb1=cb;
return function() {
if(cb1) { var cb2=cb1; cb1=false; return cb2.apply(this,arguments); }
else if(printerr)console.log(new Error('cb called twice').stack);
}
}
function simple9(cb)
{
cb=cbguard(cb);
cb();
cb();
}
#file-1-callback-basics-tutorial-js-L10
Benjamin Gruenbaum asked to clerify:
using callback doesn't turn a function to asynchronious, just into asynchronious capble.
asynchronious means the callback is executed by the system or as response to an outside event
like to setTimeout or when data read from disk is ready.
writing functions with a callback enables to use asynchronious functions inside your code.
#file-1-callback-basics-tutorial-js-L12
the short form is if(cb)cb()
when i type i start by adding cb as the last argument, then i add before end of the function if(cb)cb();
then after writing the function i check the code and return before cb()
adding return is helpful to prevent unexpected errors, which occur if the callback is inside a conditional statment (if,for,while,...) or there is code after the function. just because I am lazy to hard to debug errors later. usually i just write if(cb)return cb(); every time. except when it is obvious that it is not needed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment