Curried JavaScript function for fluent API - javascript

I want to write a default callback for HTTP-requests made with superagent. The calls are all made with the async.parallel() framework and the overall result handled together. The callback should handle the results from the HTTP-requests and return a default value, if an error occured. The default value can be specified, but null will be used if it was not set.
I want to construct my handler using a fluent syntax like this:
handle(done).withDefaultValue([]) (empty array is set as default value)
handle(done) (null is used as default value)
I'm relatively new to function currying. Here's what I've tried:
I created a Node module which should be eventually used like this:
My code in handle.js
module.exports = function(done){
this.withDefaultValue = function(defaultValue){
return function(err, result){
if(err){
debug('handling error ' + err + ' and returning default value ' + defaultValue)
return done(null, defaultValue)
}
// sanity check for null and empty objects
result = _.isEmpty(result)?[]:result
done(null, result)
}
}
return this
}
My code in somefile.js
var handle = require('handle')
async.parallel([
function(done){
api.myApiCall(arg1, arg2, handle(done).withDefaultValue([]))
},
function(done){
api.myOtherApiCall(arg1, arg2, handle(done))
}
], function(err, result){
})
Above code works for the first call (the one with withDefaultValue([]), but not for the second call:
Unhandled Error: handle(...).withDefaultValue is not a function
What am I doing wrong?

This seems to do the trick:
console.log = x => document.write(x + "<br>");
function handle(func) {
var handler = function(param) {
console.log('doing stuff...');
func(param);
};
var ret = handler.bind(this, "default");
ret.withDefault = function(val) {
return handler.bind(this, val);
}
return ret;
}
function done(param) {
console.log(param)
}
setTimeout(handle(done));
setTimeout(handle(done).withDefault(123));

Related

ExpressJS function can't return

I've been trying to develop an app working on Electron with an Express webserver. I also use mysql npm package for database stuff. But there's something wrong with the login function and I wasn't able to find the problem. I hope you could help.
server.js
function userLogin(data){
con.query(`SELECT * FROM players WHERE player_username = '${data.login_username}'`, (err, result, fields) => {
if (err) throw err;
var compare = bcrypt.compareSync(data.login_password, result[0].player_password);
if(compare == true) {
return "1";
}
else{
return "0";
};
});
};
app.route('/login').post((req,res) => {
res
.json(userLogin(req.body))
.end();
});
Everything is defined. No errors are shown but the function can't return, I don't understand why. If I add a console.log above return, it logs the result so the query is also OK, but the function doesn't return anything, literally anything.
Since userLogin is an asynchronous function, you can't just call it like a normal function and expect it to return a value. Instead, you should work on its results from inside the callback of con.query when they are available, like so:
app.route('/login').post((req, res) => {
con.query(`SELECT * FROM players WHERE player_username = '${data.login_username}'`, (err, result, fields) => {
var compare = bcrypt.compareSync(data.login_password, result[0].player_password);
res.json(compare ? "1" : "0").end();
});
});
You have to wait for the query to complete before sending the response. One way is to convert it into Promise and another is to use a callback
function userLogin(data, onComplete){
con.query(`SELECT * FROM players WHERE player_username = '${data.login_username}'`, (err, result, fields) => {
if (err) throw err;
var compare = bcrypt.compareSync(data.login_password, result[0].player_password);
if(compare == true) {
onComplete("1"); // do callback function
}
else{
onComplete("0");
};
});
};
app.route('/login').post((req,res) => {
userLogin(req.body, (val) => {res.json(val).end(); } ) // pass the function as callback
});

Function Return not Working

I am working in Node and trying to load the next sequence from my db. I am able to access the db, load and return the sequence within my function, but I am not able to access it outside of the function.
function getRunId() {
counters.findOne({_id: 'Run_ID'}, function(err, resp) {
if(err) {
console.log(err);
}
console.log('Seq: ' + resp.sequence); // Console Output = Seq: 1234
return resp.sequence;
});
};
var currentRunId = getRunId();
console.log('Run_ID: ' + currentRunId); // Console Output = CRID: undefined
I've checked several pages worth of Stack Overflow issues relating to using callback's, async (node module), how to properly return values in the function, etc... but none of them get me closer to accessing currentRunId outside of the function.
Is this issue further complicated by the use of Mongo queries inside my function?
For anyone stumbling on this later, start by reading this answer.
I've dealt with this a few times so I understand the frustration. You are trying to mix sync and async code by doing this:
var currentRunId = getRunId();
console.log('Run_ID: ' + currentRunId);
The trouble is that console.log('Run_ID: ' + currentRunId) is called immediately after you invoke getRunID() by assigning it to current RunID, and getRunID() resolves after console.log('Run_ID: ' + currentRunId), causing the currentRunId variable to be undefined.
But, you have some options to deal with this. Option one is to return a callback, and log the results of the callback instead. Option 2 is to use an ES6 promise. To use option 2, you need node version 7, and you need to use 'use strict' in your code.
Here are 3 examples built around a function stub that spoofs the results of findOne(). The getRunIdA() is your function, and getRunIdB, and getRunIdC are two example solutions to your current problem.
'use strict'
// A function stub which represents a simplified version of findOne.
// Accepts callback and returns a callback with the results of data
function findOne (callback) {
var data = {
sequence: 6
}
return callback(null, data)
}
// This is a simplified version of your function, which reproduces the undefined result
function getRunIdA () {
findOne(function (err, resp) {
if (err) {
console.log(err)
}
console.log('Seq: ' + resp.sequence)
return resp.sequence
})
}
// This is your function with a callback
function getRunIdB (callback) {
findOne(function (err, resp) {
if (err) {
console.log(err)
}
console.log('Seq: ' + resp.sequence)
return callback(resp.sequence)
})
}
// This is your function with a promise
var getRunIdC = new Promise(function (resolve, reject) {
resolve(findOne(function (err, resp) {
if (err) {
console.log(err)
}
return resp.sequence
}))
})
// Invoke your funciton; get undefined
var currentRunID = getRunIdA()
console.log('Run_ID: ' + currentRunID) // Run_ID: undefined
// Invoke getRunIdB using callback, get 6
getRunIdB(function (id) {
console.log('Run_ID: ' + id) // Run_ID: 6
})
// Invoke getRunIdC with a promise; get 6
getRunIdC.then(function (currentRunID) {
console.log('Run_ID: ' + currentRunID) // Run_ID: 6
})
/*
results for all 3:
Seq: 6
Run_ID: undefined
Seq: 6
Run_ID: 6
Run_ID: 6
*/
Give this a try by saving to your machine and running:
node test.js
Is this issue further complicated by the use of Mongo queries inside my function?
Nope, you just need to pass the results of your query to a promise or a callback so that you can work with the results somewhere else.
I hope this helps!
Edit: OP added the following code in a comment, which I will try to break down and address.
Unfortunately, using getRunIdB results in callback is not defined and using getRunIdC results in currentRunId is not defined
var currentRunID = '';
var getRunId = new Promise(function (resolve, reject) { resolve(counters.findOne({_id: 'Run_ID'}, function (err, resp) {
if (err) {
console.log(err)
}
return resp.sequence;
}))
});
getRunId.then(function (res) {
console.log('Run_ID: ' + res.sequence) // Run_ID: 1234
currentRunID = res.sequence;
})
console.log(currentRunID); // currentRunID is not defined
Check out an answer I gave to a similar question for more details on the JS concurrency model. Simply put, the getRunID() function is executing asynchronous code. What that means is that getRunID() doesn't get inserted into the message queue that determines what order javascript will execute until it's callbacks are completed. Thus, when you log currentRunID outside of the .then() function, the results is undefined because currentRunID is undefined.
I think that ultimately what OP is trying to do is to export the result of the function so that the something can be done with those results, this needs to be done within a callback like so:
getRunId.then(function (res) {
// Do stuff with the run ID here.
})
You are only returning on a callback function but not on the actual function.. Change your code to this:
function getRunId() {
var result = counters.findOne({_id: 'Run_ID'}, function(err, resp) {
if(err) {
console.log(err);
}
console.log('Seq: ' + resp.sequence); // Console Output = Seq: 1234
return resp.sequence;
});
return result; //<-- return result of your function is here
};
var currentRunId = getRunId();
console.log('Run_ID: ' + currentRunId);

Returned object undefined from module function

This has to be a scope issue that I'm not familiar with. I have a small module I've written as so:
(function () {
var getPlanInfo = function (id, conn) {
conn.query('SELECT * FROM `items` WHERE `id` = ?', [id], function (error, result) {
if (error) console.error('Query error: ' + error.stack);
console.log(result[0]); // Everything is great
return result[0];
});
};
modules.exports.getPlanInfo = function (id, conn) { return getPlanInfo(id, conn); // Typo }
})();
Here comes the problem. When I call it from anywhere (inside the module itself or another file), the return value is always undefined. I checked from within the function, the query returns the result as expected.
var backend = require('./module.js');
var t = backend.getPlanInfo();
t is undefined. This is the same if I call that method from inside the module itself (another function within that module).
I'm familiar with the callback principle in javascript and how objects and functions have to be passed around as an argument to remain in scope. Is this the issue here or is this a node.js particularity?
I tried in in the Developer Console (Chrome), works as expected.
conn.query() looks like it is async. Thus, you can't return its result from getPlanInfo() because getPlanInfo() returns long before the result is available. Returning result[0] from the conn.query() callback just returns a piece of data back into the conn.query() infrastructure. getPlanInfo() has long before already returned.
If you want an async result, then you will have to change getPlanInfo() to use a mechanism that supports getting async results such as a direct callback or a promise or something like that.
Here's a plain callback way:
var getPlanInfo = function (id, conn, callback) {
conn.query('SELECT * FROM `items` WHERE `id` = ?', [id], function (error, result) {
if (error) {
console.error('Query error: ' + error.stack);
callback(error);
return;
}
console.log(result[0]); // Everything is great
callback(0, result[0]);
});
};
modules.exports.getPlanInfo = getPlanInfo;
Then, the caller of that module would look like this:
var m = require('whatever');
m.getPlanInfo(id, conn, function(err, result) {
if (err) {
// error here
} else {
// process result here
}
});
You don't return anything from getPlanInfo. Probably you wanted to write modules.exports.getPlanInfo = function (id, conn) { return getPlanInfo; }
(with return getPlanInfo; instead of return getPlanInfo();)

Significance and Reason for using a callback?

Async and callbacks are still concepts I struggle with. I have been reading and trying to make sense of code that uses them to gain more familiarity. I don't understand why this snippet of code uses a callback instead of just passing in a value (instead of an anonymous function with the argument as the value to be evaluated).
Shorten.prototype.Lookup = function (url, callback){
var mongoose = this.app.locals.settings.mongoose;
var Cursor = mongoose.model('urls');
Cursor.find({"linkFinal": url}, function(err, docs){
if (err === null){
if (typeof(callback) === "function"){
if (docs[0] !== undefined){
callback(docs[0]);
return docs[0];
}else{
callback(false);
return false;
}
}
}else{
console.log(err);
}
});
};
This is the code function in a use case:
shorten.LookUp(url, function(urlCheck){
if (urlCheck === false){
//Add to db
// another function that uses a callback as second parameter
shorten.addLink(shortenURL, shortURL){
small.shortenedURL = "http://" + domain + "/" + shortURL.link;
small.shortenError = false;
small.alreadyShortened = false;
res.json(small);
});
}else{
small.shortenedURL = "http://" + domain + "/" + urlCheck.link;
small.shortenError = false;
small.alreadyShortened = true;
res.json(small);
}
});
My only intuition for reason on why to use a callback function in this scenario is that when the query is made to the Database (in this case mongodb using Mongoose api) the function is only run when the data is available(which is the case with:
// after the result of the query is ran, the result is passed to the function in
// the docs parameter.
Cursor.find({"linkFinal": url}, function(err, docs){
This is the main reason I can come up with on why a callback is passed instead of just a value. I am still not 100% certain on why this wouldn't work:
Shorten.prototype.LLookUp = function (url, value){
var mongoose = this.app.locals.settings.mongoose;
var Cursor = mongoose.model('urls');
Cursor.find({"linkFinal": url}, function(err, docs){
if (err === null){
if (docs[0] !== undefined){
return docs[0];
}else{
value = false;
return false;
}
}
}else{
console.log(err);
}
});
};
All I did in this scenario was remove the the anonymous function and passed the argument of that function instead.
Why is a callback preferred in the first code snippet over just
passing an argument?
Would the third code snippet be a viable option? If not, why?
Thanks!
The return statements are inside the callback function, not the LLookUp function. So they will return to whoever calls the callback (mongoose library), however mongoose library doesn't do anything with the value the callback returns so it's useless to return any value.

Returning from anonymous function passed as parameter

Consider the following code:
function dbTask(q) {
mysql = new MySQL();
mysql.host = sqlHost;
mysql.user = sqlUser;
mysql.password = sqlPassword;
mysql.query("USE stock");
return mysql.query(q, function(err, results, fields) {
if(err) {
console.log("MySQL Error: " + err + ", Query: " + q);
return false;
} else {
return results; //here
}
});
};
var r = dbTask("SELECT * FROM user;");
console.log(r);
Whereas, I want the results to be returned from the inner anonymous function when calling dbTask() in the second last line, I am getting different output which seems like some internal construct of the mysql library under use.
How can I get dbTask to return the results when called?
Since mysql.query is asynchronous, then you'll have to re-think your architecture.
Instead of having your function return true or false, you'll have to pass in handlers to be called if the query returned true or false.
Something like this:
function dbTask(q, success, failure) {
mysql = new MySQL();
mysql.host = sqlHost;
mysql.user = sqlUser;
mysql.password = sqlPassword;
mysql.query("USE stock");
mysql.query(q, function(err, results, fields) {
if(err) {
console.log("MySQL Error: " + err + ", Query: " + q);
failure(err, results, fields);
} else {
success(results, fields);
}
});
};
Which you'd call like this:
dbTask(q,
function(results, fields) { /* ... */ },
function(err, results, fields) { /* ... */ });
You can't because dbTask returns once the mysql.query call completes to initiate the query. So by the time mysql.query's callback is called, console.log(r) has already executed.
This is the crux of the asynchronous nature of node.js.
What you can do is have dbTask accept a callback parameter that the function calls once results is available so that it can be provided to the caller asynchronously.

Categories

Resources