How to add extra parameters to a function callback - javascript

I am using a few callbacks in an app that I'm writing. I am using Mongoose models and need to save a few different places. The save function takes a callback, and the callback gets error and model for its parameters, but I'd like to send the callback an extra parameter that the function needs. I'm not sure of the proper syntax to be able to do this. Below is some example code of what I'm going for...
var saveCallBack = function(err, model, email_address){
if(err) {
//handle error
}
else {
//use the third parameter, email_address, to do something useful
}
};
Below, token is a mongoose model. As I said, save takes a callback and gets passed error and model, but I'd like to also send my callback a variable email_address that I figure out at some other point. Obviously the appendParameter function is pseudo-code, but this is the type of functionality that I need.
token.save(saveCallBack.appendParameter(email_address));

If you make that the first parameter instead, you can use .bind().
token.save(saveCallBack.bind(null, email_address));
var saveCallBack = function(email_address, err, model){};

I'm using bind function for appending additional parameters for callbackes
var customBind = function (fn, scope, args, appendArgs) {
if (arguments.length === 2) {
return function () {
return fn.apply(scope, arguments);
};
}
var method = fn,
slice = Array.prototype.slice;
return function () {
var callArgs = args || arguments;
if (appendArgs === true) {
callArgs = slice.call(arguments, 0);
callArgs = callArgs.concat(args);
} else if (typeof appendArgs == 'number') {
callArgs = slice.call(arguments, 0);
}
return method.apply(scope || window, callArgs);
};
}
This customBind function accepts four arguments, first one is original callback function, second is the scope, third is additional parameters (array), and fourth is flag append or replace. If you set last parameter to false than only parameters in array will be available in this function.
and with this function you can simple add new parameters or to override the existing one
var callback = customBind(saveCallBack, this, [array_of_additional_params], true)
in this way all original parameters remain and your parameter will be appended to the end.

No matter how many parameter you defined, the callee will always pass the same parameter inside its process.
but it will be more simple, just use a variable that is visible from outside of the callback.
Eg:
var email = 'yourmail#mail.com';
var saveCallBack = function(err, model){
if(err) {
//handle error
}
else {
alert(email);
}
};
Updated (#Jason): then you can use Immediately-Invoked Function Expression (IIFE)
(function(mail){
var saveCallBack = function(err, model){
if(err) {
//handle error
}
else {
alert(mail);
}
};
token.save(saveCallBack);
}, emailAddress);

Related

How to prevent bind from mutating original object in node.js when using async.series?

I have the following test code:
var async = require('async');
var GROUP = 'testGroup';
var opts = {
someKey: 'hi',
};
test(opts);
function test(options) {
async.series([
doThis.bind(null, options),
doThat.bind(null, options),
], function(results) {
debugger;
});
}
function doThis(options, cb) {
options.someKey = [GROUP, options.someKey].join('.');
return cb();
}
function doThat(options, cb) {
debugger;
options.someKey = [GROUP, options.someKey].join('.');
return cb();
}
When we hit the debugger in doThat(), options.someKey already has the value someGROUP.hi, so when the function finishes we end up with options.someKey === 'someGROUP.someGroup.hi'
How do we bind such that the original object does not change? The bind is necessary because we need to pass in options to the functions that run within async.series. Otherwise, we could just invoke the functions and pass in the object as a parameter.
I'm don't think your partially applying the options parameter to your doThis(), doThat() functions is especially pertinent.
You're passing the same javascript object/literal as aparameter to two functions and and then mutate that parameter.
If you don't to mutate that object then don't. Find some other way of returning the results of your operation. doThis() and doThat() could return values instead of modifying the parameter. You could gather them up in the final callback after the series gets called.
If you just want to preserve the intital value of opts, use lodash or something to make a deep clone of opts before you pass it into test.

node async waterfall using an array with callbacks that have arguments

I am getting an error that I do not understand. I am calling async.waterfall with an array of functions. The function is 'shortened' for clarity.
FabricCommand.prototype.do = function (callback, undoArray) {
var self = this;
if (undoArray === undefined) {
undoArray = [];
}
undoArray.push(self);
callback(null, undoArray);
};
I create the array as listed below: doCommands is an array and the objects are added as such:
doCommands.push(fabricCommand.do.bind(fabricCommand));
the waterfall setup:
async.waterfall(
doCommands,
function(err, undoCommands){
if (err) {
// do something ...
}
else {
console.log('we succeeded with all the do commands... and there are '
+ undoCommands.length
+ ' in the undoCommands but we will disregard it...');
}
}
);
Now when I run this code, the first time through the FabricCommand.do function, I allocate the undoCommands array and I add one to it, next time through I get, where I try to add the array element, the following error:
undoArray.push(something);
^ TypeError: Object function (err) {
if (err) {
callback.apply(null, arguments);
callback = function () {};
}
else {
var args = Array.prototype.slice.call(arguments, 1);
var next = iterator.next();
if (next) {
args.push(wrapIterator(next));
}
else {
args.push(callback);
}
async.setImmediate(function () {
iterator.apply(null, args);
});
}
} has no method 'push'
Can anyone see what I am doing wrong?
The function that is executed by async.waterfall must have the following signature:
function(arg, callback) { … }
or, with multiple arguments:
function(arg1, arg2, callback) { … }
In your case, you simply inverted the two parameters:
FabricCommand.prototype.do = function (callback, undoArray) { … }
callback received the value intended to be stored in undoArray, and undoArray received the value intended for the callback, i.e. a function: that's why you encountered this weird error (function […] has no method 'push').
You need to put the parameters in the correct order:
FabricCommand.prototype.do = function (undoArray, callback) { … }
A second issue is that the first function of the waterfall receives only one parameter: the callback (because there is no value to be received, as it is the first function of the waterfall). A solution is to check the number of arguments:
if (Array.prototype.slice.apply(arguments).length === 1) {
callback = undoArray;
undoArray = undefined;
}
Here is a working gist.

Calling a Javascript function with an unknown number of arguments with original context

I'm currently using Function.apply to call a function with a dynamic number of arguments, but I do not have access to the original context, and have no desire to set the context myself. What I would like, is to be able to call a function with a variable number of arguments, keeping the original context.
Perhaps some code should show you what I'm trying to do:
function MulticastDelegate() {
var handlers = [];
this.event = {
subscribe: function(handler) {
if (typeof(handler) === 'function') {
handlers.push(handler);
}
},
unsubscribe: function(handler) {
if (typeof(handler) === 'function') {
handlers.splice(handlers.indexOf(handler),1);
}
}
}
this.execute = function() {
var args = Array.prototype.slice.call(arguments);
for (var handler in handlers) {
// call this with the original context of the handler
handlers[handler].apply(null, args);
}
}
}
Essentially, I want the behaviour of apply - the ability to pass an array of arguments - without the behaviour of call - changing the context under which a function executes.
There is no such thing as “the original context” of a function. You would have to do something like this:
subscribe: function(handler, context) {
if (typeof(handler) === 'function') {
handlers.push([handler, context]);
}
},
And then, of course,
handlers[handler][0].apply(handlers[handler][1], args);
Alternatively (this is what I would do), leave it to the caller to make sure the handler has the right context. For example, instead of delegate.subscribe(this.foo), say
var self = this
delegate.subscribe(function () { self.foo() })
Or, using Function.prototype.bind,
delegate.subscribe(this.foo.bind(this))
Could it be, that .bind would solve the case?
http://documentcloud.github.com/underscore/#bind
In this case, you get a function 'binded' to the original context

js callback function

How can I callback to a function in an object?
json_post('get_tracks', 'json.request.php?get=tracks', 'genreId='+id+'&perPage=70&page=1', 'rtn_tracks');
Instead of making a callback to rtn_tracks() I want to do it to this.rtn()
How can I define this in the callback string?
Here is the code:
function stream_tracks(){
this.get = function(id){
json_post('get_tracks', 'json.request.php?get=tracks', 'genreId='+id+'&perPage=70&page=1', 'rtn_tracks');
};
this.rtn = function(json_obj){
this.cnstr(json_obj);
};
this.cnstr = function(json_obj){
alert('test');
};
}
Stream_tracks = new stream_tracks();
var XMLHTTP = {};
function json_post(request_uid, uri, get_str, callback_function, callback_var){
request_uid += Math.floor(Math.random()*999999).toString();
if(window.XMLHttpRequest){
XMLHTTP[request_uid] = new XMLHttpRequest();
}
else if(window.ActiveXObject){
XMLHTTP[request_uid] = new ActiveXObject('Microsoft.XMLHTTP');
}
XMLHTTP[request_uid].open('POST', uri, true);
XMLHTTP[request_uid].setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
XMLHTTP[request_uid].onreadystatechange = function(){
if(XMLHTTP[request_uid].readyState == 4){
if(callback_function){
eval(callback_function+'('+XMLHTTP[request_uid].responseText+(callback_var ? ', callback_var':'')+')');
}
}
}
XMLHTTP[request_uid].send(get_str);
}
Instead of using a string for callback, use a method.
var my = {
start : function (s, callback) {
callback(s);
},
callback: function(s) {
}
}
You cannot use:
my.start("Hello World", my.callback)
Since this will cause the method to be processed without connection to the object my but you can do this.
my.start("Hello World", function(s) { my.callback(s); });
You can pass functions as objects in Javascript, you don't need to pass the function name and use eval. If your callback should be called as a member of a class, you need to pass along the class instance as well. E.g. add a callback context argument to your json function:
function json_post(request_uid, uri, get_str, callback_function, callback_var, callback_context){
/*... snip ...*/
XMLHTTP[request_uid].onreadystatechange = function(){
if(XMLHTTP[request_uid].readyState == 4)
{
if(callback_function){
/* The call() function lets you call a function in a context */
callback_function.call(
callback_context || this,
XMLHTTP[request_uid].responseText,
callback_var
);
}
}
};
XMLHTTP[request_uid].send(get_str);
}
Then you would call it like so:
json_post('get_tracks', 'json.request.php?get=tracks', 'genreId='+id+'&perPage=70&page=1',
this.rtn, // callback function
null, // callback var
this // callback context
);
Here's a good tip though: Use a framework! It will make your day a lot easier.
Ok so there are a couple of things that you need to do and it might make more sense if you have a read about closures.
Firstly you'll need to make a reference to the this variable so you can access it inside your callback without overwritting it.
function stream_tracks(){
var obj = this;
Then if you want to refer to properties of that class/object from within its other methods you just use obj.this.
The second thing you should do is pass the callback as a function not as a string. It will also be more efficient as you will be able to do away with the eval function.
this.get = function(id){
json_post(
'get_tracks',
'json.request.php?get=tracks',
'genreId='+id+'&perPage=70&page=1',
function(){ obj.rtn(); }
);
};
Wrapping the callback in the anonymous function forms the closure and allows the function to use the variables from class. Also if you do it this way you can pass any parameters through at the same time as the function and do away with the extra parameters in the parent function.

Javascript callback function and parameters [duplicate]

This question already has answers here:
Pass an extra argument to a callback function
(5 answers)
Closed 6 years ago.
I want to something similar to this:
function AjaxService()
{
this.Remove = function (id, call_back)
{
myWebService.Remove(id, CallBack)
}
function CallBack(res) {
call_back(res);
}
}
so my calling program will be like this:
var xx = new AjaxService();
xx.Remove(1,success);
function success(res)
{
}
Also if I want to add more parameters to success function how will I achieve it.
Say if I have success function like this:
var xx = new AjaxService();
//how to call back success function with these parameters
//xx.Remove(1,success(22,33));
function success(res,val1, val2)
{
}
Help will be appreciated.
Use a closure and a function factory:
function generateSuccess (var1,var2) {
return function (res) {
// use res, var1 and var2 in here
}
}
xx.Remove(1,generateSuccess(val1,val2));
What you're passing here is not the generateSuccess function but the anonymous function returned by generateSuccess that looks like the callback expected by Remove. val1 and val2 are passed into generateSuccess and captured by a closure in the returned anonymous function.
To be more clear, this is what's happening:
function generateSuccess (var1,var2) {
return function (res) {
// use res, var1 and var2 in here
}
}
var success = generateSuccess(val1,val2);
xx.Remove(1,success);
Or if you prefer to do it inline:
xx.Remove(1,(function(var1,var2) {
return function (res) {
// this is your success function
}
})(val1,val2));
not as readable but saves you from naming the factory function. If you're not doing this in a loop then Xinus's solution would also be fine and simpler than my inline version. But be aware that in a loop you need the double closure mechanism to disconnect the variable passed into the callback function from the variable in the current scope.
You can pass it as anonymous function pointer
xx.Remove(1,function(){
//function call will go here
success(res,val1, val2);
});
one way to do this:
function AjaxService {
var args_to_cb = [];
this.Remove = function (id, call_back, args_to_callback_as_array) {
if( args_to_callback_as_array!=undefined )
args_to_cb = args_to_callback_as_array;
else
args_to_cb = [];
myWebService.Remove(id, CallBack)
}
function CallBack(res) {
setTimeout( function(){ call_back(res, args_to_cb); }, 0 );
}
}
So you can use it like this:
var service = new AjaxService();
service.Remove(1,success, [22,33]);
function success(res,val1, val2)
{
alert("result = "+res);
alert("values are "+val1+" and "+val2);
}
I usually have the callback execute using a setTimeout. This way, your callback will execute when it gets the time to do so. Your code will continue to execute meanwhile, e.g:
var service = new AjaxService();
service.remove(1, function(){ alert('done'); }); // alert#1
alert('called service.remove'); // alert#2
Your callback will execute after alert#2.
Of course, in case of your application, it will happen so automatically since the ajax callback itself is asynchronous. So in your application, you had better not do this.
Cheers!
jrh

Categories