I am trying to create a class that in its constructor uses some helper functions. Is there a way to move these helpers to the prototype? The problem is that the constructor does some asynchronous calls to the database and I need to apps in a callback function so I can continue execution after the data was retrieved.
I want to move stuff to the prototype, because if I understood correctly, these functions are not tied to a single object, so if I have multiple objects they will still call the same code but with different context.
Class = function(id, callback) {
var that = this,
addCore = function(err, model) {
that.id = model._id
that.core = model
callback(err, that)
},
addTopology = function() {
}
if (arguments.length === 2) {
gameOps.findById(id, addCore)
} else {
callback = id
gameOps.create(addCore)
}
}
Class.prototype = {
addPlayer: function(player, callback) {
gameOps.addPlayer(player, this.model, callback)
}
}
I want to move stuff to the prototype, because if I understood correctly, these functions are not tied to a single object, so if I have multiple objects they will still call the same code but with different context.
Yes. However, that is not what you want: The asynchronous callbacks need to be tied on specific instances.
If you don't want to have too much stuff floating around your constructor, you might reconsider your design:
function Class(model) {
this.id = model._id;
this.core = model;
}
Class.prototype.addPlayer = function(player, callback) {
gameOps.addPlayer(player, this.model, callback);
};
Class.fromDatabase = function(id, callback) {
function addCore(err, model) {
if (err)
callback(err);
else
callback(null, new Class(model))
}
function addTopology() {
}
if (arguments.length === 2) {
gameOps.findById(id, addCore)
} else {
callback = id
gameOps.create(addCore)
}
}
Related
I want to create a chain of asynchronous methods in Node.js, for example:
function functionA(data, callback){
console.log("A")
// do something here
callback();
}
function functionB(data, callback){
console.log("B");
// do something here
callback();
}
function functionC(data, callback){
console.log("C");
// do something here
callback();
}
each function is independent, but when chained, they can be called orderly. For example:
functionA(data).functionC(data).functionB(data)
will print A then C then B. The chain's order can be re-arranged without restriction.
Here's what I have been searched:
Create chained methods in node.js?, --> here's the previous question regarding this very topic, very old, also not async
http://www.dustindiaz.com/async-method-queues/ --> depends on jquery, client side script, not node, not async
https://github.com/FuturesJS/FuturesJS/tree/v3 --> deprecated, the newer version (3) with chainify feature unfinished. Looks like an abandoned project
I use async a lot, I know about waterfall, series, and many functions that I use regularly, I just want to re-arrange it to something simpler to use in multiple places. I think about how to "chaining" them, so that in other file I can use the same method.
Here's my reason why async is not the answer that I expect.
Consider accounting problem, if there's a sell transaction with total $1200, first I had to put $1200 into asset:cash book(debit side), then I had to put $1200 into income book(credit side). Is it do-able with async? yes of course. it'll look like this:
async.series([
function(callback){
bookLibrary.debit('cash', 1200, callback);
},
function(callback){
bookLibrary.credit('income', 1200, callback);
}
]);
so I want to make it much simpler and easier to read by chaining them, like this:
bookLibrary.debit('cash', 1200).credit('income', 1200)
First off, to chain a bunch of functions, you need them to be configured as methods on a common object. Then, each method can return that object so the return result is the object and the next method can be called on that object.
So, to do something like this:
a().b().c();
You need a() to return an object that has the method b() on it as a property. And, similarly you need a().b() to return an object that has c() on it as a property. This is generally called the Fluent Interface. That's all pretty straightforward by itself for synchronous methods. This is exactly how jQuery does its chaining.
$(".foo").show().css("color", "blue");
All three of these calls all return a jQuery object and that jQuery object contains all the methods that you can chain.
In the example above, you could do synchronous chaining like this:
function a() {
}
a.prototype = {
b: function() {
// do something
return this;
},
c: function() {
// do something else
return this;
}
};
But, your question is about asynchronous operations. That is significantly more work because when you do:
a().b().c();
That's going to execute all three methods immediately one after the other and will not wait for any of them to complete. With this exact syntax, the only way I know of to support chaining is to build a queue where instead of actually executing .b(xxx) right away, the object queues that operation until a() finishes. This is how jQuery does animations as in:
$(".foo").slideUp().slideDown();
So, the object that is returned from each method can contain a queue and when one operation completes, the object then pulls the next item from the queue, assigns it's arguments (that are also held in the queue), executes it and monitors for that async operation to be done where it again pulls the next item from the queue.
Here's a general idea for a queue. As I got into this implementation, I realized that promises would make this a lot easier. Here's the general idea for an implementation that doesn't use promises (untested):
For simplicity of example for async operations, lets make a() execute a 10ms setTimeout, .b() a 50ms setTimeout and .c() a 100ms setTimeout. In practice, these could be any async operations that call a callback when done.
function a() {
if (!(this instanceof a)) {
return new a();
} else {
this.queue = [];
this.inProgress = false;
this.add(function(callback) {
// here our sample 10ms async operation
setTimeout(function() {
callback(null);
}, 10);
}, arguments);
}
}
a.prototype = {
b: function() {
this.add(function(callback) {
// here our sample 50ms async operation
setTimeout(function() {
callback(null);
}, 50);
return this;
}, arguments);
},
c: function(t) {
this.add(function(t, callback) {
// here our sample 100ms async operation
setTimeout(function() {
callback(null);
}, t);
return this;
}, arguments);
},
add: function(fn, args) {
// make copy of args
var savedArgs = Array.prototype.slice.call(args);
this.queue.push({fn: fn, args:savedArgs});
this._next();
},
_next: function() {
// execute the next item in the queue if one not already running
var item;
if (!this.inProgress && this.queue.length) {
this.inProgress = true;
item = this.queue.shift();
// add custom callback to end of args
item.args.push(function(err) {
this.inProgress = false;
if (err) {
// clear queue and stop execution on an error
this.queue = [];
} else {
// otherwise go to next queued operation
this._next();
}
});
try {
item.fn.apply(this, item.args);
} catch(e) {
// stop on error
this.queue = [];
this.inProgress = false;
}
}
}
};
// usage
a().b().c(100);
If we use promises for both our async operations and for the queuing, then things get a bit simpler:
All async operations such as firstAsyncOperation and secondAsyncOperation here return a promise which drastically simplifies things. The async chaining is done for us by the promise infrastructure.
function a(arg1, arg2) {
if (!(this instanceof a)) {
return new a(arg1, arg2);
} else {
this.p = firstAsyncOperation(arg1, arg2);
}
}
a.prototype = {
b: function() {
return this._chain(secondAsyncOperation, arguments);
},
c: function() {
return this._chain(thirdAsyncOperation, arguments);
},
_chain: function(fn, args) {
var savedArgs = Array.prototype.slice.call(args);
this.p = this.p.then(function() {
return fn.apply(this, savedArgs);
});
return this;
},
then: function(a, b) {
this.p = this.p.then(a, b);
return this;
},
catch: function(fn) {
this.p = this.p.catch(fn);
return this;
}
};
// usage:
a().b().c(100).then(function() {
// done here
}).catch(function() {
// error here
});
You can use async waterfall to do that, this should meet your requirement.
async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});
You can do this by having all your functions enclosed in a single object , just like below.
var Ext = {
function a() {
return this;
}
function b() {
return this;
}
}
then you can call them as below
Ext.a().b();
for detailed example please look at the code of my javascript library which does exactly what you need https://github.com/waqaskhan540/MapperJs
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);
When I pass functions to mongoose, it seems it no longer has a reference to this. Is there a better way to go about this? All functions are simplified for length reasons. I cannot edit the function getUsernameForId to take additional parameters.
I have class:
var class = new function() {
this.func1 = function(data) {
return data + "test";
}
this.func2 = function(data) {
var next = function(username) {
return this.func1(username); // THIS THROWS undefined is not a function
}
mongoose.getUsernameForId(1, func3);
}
}
mongoose is another class like this:
var getUsernameForId = function(id, callback) {
user_model.findOne({"id": id}, function(err, user) {
if(err) {
throw err;
}
callback(user.username);
});
}
How do I resolve the undefined is not a function error. I do not want to duplicate code because func1 is pretty long in reality.
It's not clear from your code how next is used, but if you need it to be invoked with correct this you can try to use Function.prototype.bind method:
this.func2 = function(data) {
var next = function(username) {
return this.func1(username);
}.bind(this);
mongoose.getUsernameForId(1, func3);
}
I assume that you simplified code for the post and next does more things in reality. But if it indeed just returns result of this.func1 then you could shorten it:
var next = this.func1.bind(this);
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.
I have a requirement where I get the anchor tags id and based on the id I determine which function to execute.. so is there anything that suites below code
function treeItemClickHandler(id)
{
a=findDisplay(id);
a();
}
You can assign a function to a variable like so:
You can also return a function pointer from a function - see the return statement of findDisplay(id).
function treeItemClickHandler(id)
{
var a= findDisplay;
var other = a(id);
other();
}
function findDisplay(id)
{
return someOtherThing;
}
function someOtherThing()
{
}
Sure, functions are first class objects in JavaScript. For example, you can create a map (an object) which holds references to the functions you want to call:
var funcs = {
'id1': function(){...},
'id2': function(){...},
...
};
function treeItemClickHandler(id) {
if(id in funcs) {
funcs[id]();
}
}
As functions are treated as any other value, you can also return them from another function:
function findDisplay(id) {
// whatever logic here
var func = function() {};
return func;
}
functions are normal javascript values, so you can pass them around, (re)assign them to variables and use them as parameter values or return values for functions. Just use them ;) Your code is correct so far.
You can map between ids and functions to call in a number of ways.
One of the simpler ones is to create an object mapping ids to functions, and find the function to call from that object (this is in essence a nicer-looking switch statement).
Example:
function treeItemClickHandler(id)
{
var idMap = {
"some-id": findDisplay,
"another-id": doSomethingElse
};
if (!idMap.hasOwnProperty(id)) {
alert("Unknown id -- how to handle it?");
return;
}
// Call the corresponding function, passing the id
// This is necessary if multiple ids get handled by the same func
(idMap[id])(id);
}
function findDisplay(id)
{
// ...
}
function doSomethingElse(id)
{
// ...
}