accessing method of the class inside of async module or - javascript

I just noticed if I have a method in my class I cannot access it with this keyword in my other prototypes if I call them in async.auto or... please see the sample code for more clarification and my workaround.
Would you do the same? In other words is this the most elegant way in Node.JS?
function foo(config) {
var self = Object.create(foo.prototype)
self.db = nano.db.use(config.db.dbName);
return self
}
foo.prototype.method1 = function () {
// The workaround to use this.db is to store this.db in a variable, is this elegant?!? Would you do the same?
var db = this.db;
async.auto({
check_DB: function (next) {
// do some operations here
next();
},
insert_DB: ['check_DB', function (callback, results) {
// Note1: interestingly this.db is not going to work! in other words here this.db is undefined
db.insert(value, function (err, body) {
//Do some other operations here
})
}]
});
}
foo.prototype.method2 = function () {
// The workaround to use this.db is to store this.db in a variable?!? Would you do the same?
var db = this.db;
db.get("baz", function (err, body) {
// Do some operatiuons
// Note2: interestingly this.db is not going to work here either!
db.get("bar", function (err, response) {
// do some other operations
})
}
});
}

It's exactly right. The async.auto(... function is going to change the scope of "this". It's not particular to async, but to calling a javascript function within another one.
Same deal with db.get(... in method2. It also changes the scope of "this" in the exact same way.
So it is quite normal in javascript code before calling a function where you want to access "this" from the outer scope to assign "this" to some other variable just, just as you've done with:
var db = this.db;
A lot of folks will assign this to something like: "self', "_this", "that", etc.

Related

How to name an anonymous function or a express middleware in JavaScript?

Here's the middleware that I use in express:
const app = express();
const port = 8000;
const f = () => {
return async (req, res, next) => {
await new Promise(resolve => setTimeout(resolve, 3000));
return next();
}
}
const namedFunction = f();
app.use(namedFunction); // earlier I was using `app.use(f());`
But my function still appear as anonymous function in profiler:
Something like this:
A bit of background:
We want to see which middleware is causing the high latency, but because the middlewares are anonymous, we can't narrow down the cause in our APM + JS profiler. The preceding is just one example; we use approximately 40 middleware packages over which we have no control.
That's why I thought passing f() to namedFunction should fix the issue but it wasn't so looking for help on this.
Other tries till now:
As per Jonsharpe's comment I tried:
app.use(function namedFunction() { f()(...arguments) });
But in profiler it still appear as an anonymous function
While I do not know much about express, I can at least clear up a misconception you have about anonymous functions.
When you create a function and immediately store it in a variable, the interpreter can implicitly use the name of the variable as the name of the function. That is true of both functions created with the function expression or with the arrow notation.
const foo = () => {};
foo.name; // "foo";
const bar = function () {};
bar.name; // "bar";
But if you create a function and then immediately pass it as an argument or return it, the interpreter cannot give it a name at creation, so it becomes anonymous.
So when you did this:
const namedFunction = f();
All you did was store the already-created anonymous function returned by f() inside the variable namedFunction. You did not actually give it a name. To do that, you would need to do something like so:
const unnamedFunction = f();
const namedFunction = (...args) => unnamedFunction(...args);
Which simply wraps the unnamed function into a named one.
You could also create a variable to store an arrow function before returning it or passing it as a callback.
const meaningfulName = () => { ... };
return meaningfulName;
But unless you actually rely on the behavior of arrow functions, I would just use a named function expression in those cases:
return function meaningfulName () { ... };
In this answer, an example is shown that redefines the name property of a function which is normally read-only. This seems to work just fine in v8.
// Apart from the added error so we can log a stack trace,
// this is unchanged from your example:
const f = () => {
return async (req, res, next) => {
throw new Error("Intentionally cause an error to log the function's name.");
await new Promise(resolve => setTimeout(resolve, 3000));
return next();
}
}
const namedFunction = f();
When the function is called and its error is logged, you'd get a stack trace like this, as you saw in your profiler, the function has no name:
namedFunction().catch(console.log);
// Error: Intentionally cause an error to log the function's name.
// at /tmp/namedfn.js:3:19
// at Object.<anonymous> (/tmp/namedfn.js:9:5)
Rename as per the linked answer:
Object.defineProperty(namedFunction, 'name', {value: 'someFn'});
namedFunction().catch(console.log);
// Error: Intentionally cause an error to log the function's name.
// at someFn (/tmp/namedfn.js:3:19)
// at /tmp/namedfn.js:14:9
It is now named 'someFn' and should show up in your profiler as such.
Note that this answer is better / less hacky in cases where the source can be edited (OP doesn't have control over f()'s content according to a comment).
After a lot many tries of assigning name, refactoring use I came up with this and finally the profiler was able to point out that it's the wrappedFunction which is causing it so going ahead I'll need to create a wrapperFunction for each of the case.
Here's a sample of what worked in the end:
const f = () => {
return async (req, res, next) => {
await new Promise(resolve => setTimeout(resolve, 3000));
return next();
}
}
const wrappedFunction = async(req, res, next) => {
await new Promise(resolve => f()(req, res, resolve)); // Now since time is spent in this block that's why profiler is picking this up instead of the anonymous function as the main resource consuming function
next();
}
app.use(wrappedFunction);
And here's what it looks like in profiler now:
Just an note to others who might not know the context:
By default the official middlewares are usually named functions but some 3rd party middleware return an anonymous function which profiler/APM isn't able to pick up and point to code block from there.
That's why it's important to have a named function instead of anonymous middleware showing up in UI and being unclear where to look at.

How to break some logic out of an async callback?

I'm trying to make an api call using the callback method in request, but I'm new to web development and I'm stuck on async at the moment. I've got the following working, but I want to break the logic out some. For example, here's what is working currently
const request = require('request');
class GetAllData{
constructor(){
this.loadData();
}
// Gets all data from api query that I'll need to build everything else I'll need
data(callback){
request({'url':`https://definitely/a/url.json`, 'json': true }, function (error, response, body) {
callback(body);
});
}
loadData(cmrUrl){
console.log("loadData");
this.data(function(result){ console.log(result.foo.bar)});
}
}
var moreData = new GetAllData();
This works, and I can do the two things I need, which are log some results, and make a small calculation with the results. However, if I want to break this logic out into other functions, I get some errors.
const request = require('request');
class GetAllData{
constructor(){
this.loadData();
// Member variables
this._subsetOne;
this._thingICalculated;
// Function call to print data outside of the async call.
this.printData(this._subsetOne, this._thingICalculated);
}
// Gets all data from api query that I'll need to build everything else I'll need
data(callback){
request({'url':`https://definitely/a/url.json`, 'json': true }, function (error, response, body) {
callback(body);
});
}
loadData(cmrUrl){
console.log("loadData");
// Set a class member variable
// ERROR: 'this' is undefined
this.data(function(result){ this._subsetOne = result.foo.bar)};
// Call a member function, which calculates something, and then sets a member variable.
this.calculateSomething = result;
console.log(result);
};
}
// Function which takes in result from async call, then calculates something.
set calculateSomething(result){
this._thingICalculated = result + 1;
}
printData(x, y){
console.log(x,y);
}
}
var moreData = new GetAllData();
From what I've been reading the issues I'm hitting are pretty common, but I'm still not understanding why this isn't working since the call is asyncronous, and I'm just trying to set a variable, or call a function. I'm assuming there's some way to ask the member variable setting and function call to await the completion of the async request?
Fix attempt one
const request = require('request');
class GetAllData{
constructor(){
this.loadData();
this._subset;
}
// Gets all data from api query that I'll need to build everything else I'll need
data(callback){
request({'url':`https://definitely.a.url/yep.json`, 'json': true }, function (error, response, body) {
callback(body);
});
}
loadData(cmrUrl){
console.log("loadData");
this.data(function(result){ this._subset = result
this.getSubset()}.bind(this));
}
getSubset(){
console.log(this._subset);
}
}
var moreData = new GetAllData();
Subset ends up being undefined.
In the constructor, you have to bind any member method that uses this to itself. So before calling loadData:
this.loadData = this.loadData.bind(this);
Also seems like this.data will get its own scope in loadData, which is why this returns undefined inside that function. Thus you have to bind this.data to this (your class instance) as well.
And do the same for any method that accesses this. The problem is classic JavaScript functions by default have an undefined scope. Arrow functions however automatically inherit the scope of the caller.

Confusion about this in callbacks

I am really confused about the value of this in callbacks, well I know that the value of this is always taken from where it was called, but here in the below code, i can't figure out what's happening.
userSchema.pre("save", function(next) {
let user = this;
bcrypt.hash(user.password, 10, function(err, hash) {
if (err) {
next(err)
} else {
user.password = hash;
next();
}
})
});
I was watching a tutorial of node js with mongoose, so the instructor said:
In the context of this callback function, the word this refers to the object, which is
let userSchema = new mongoose.Schema({
password: {
type: String,
required: true
}
});
shouldn't the user = this here refer to Node Global Object instead of that object?
so I'm really confused how this happens, for Example, if I try to mimic this code behaviour in simple JavaScript.
function UserSchema() {
this.pre = function(cb) {
return cb();
}
}
function Bycrypt() {
this.hash = function(cb) {
return cb();
}
}
userSchema.pre(function() {
var user = this;
bycrypt.hash(function() {
console.log(user)
})
});
this will log user as the Window Object because the callback function was called in the context of Window.
well, I know that it's a weird question to ask.
this in JavaScript functions refers to execution context and in a case of normal (not ES6-specific "arrow" function) normally equals to context, function was called in. However "this" can be changed by e.g. using bind, call or apply methods of the Function object to pass different value for this.
You can read, for example, this article on MDN for details.
This is not a weird question at all, the this keyword is often a source of confusion because of its implicit nature :-) Anyway, I have no idea about how the pre function is implemented, but we can easily imagine something like that :
schema = {
whoami: "a schema"
};
schema.pre = function (cb) {
return cb.call(this);
}
schema.pre(function () {
console.log(this.whoami);
});
The call function is a JavaScript builtin that allows to change the default subject (window) of a function. Browsing the source code I have found something that seems to be the pre function (not 100% sure) : https://github.com/Automattic/mongoose/blob/master/lib/schema.js#L1138. As you can see they use the apply builtin which has the same effect as call. Let's see how they differ :
function f(a, b) {
console.log("I am", this.whoami);
console.log("Arguments :", "[" + a + ", " + b + "]");
}
f.call({ whoami: "A" }, "b", "c");
f.apply({ whoami: "X" }, ["y", "z"]);
I believe that this can be a good starting point to investigate. I let you dive into the source code to find the nature of this.s.hooks and this.queue(...) :-)
this binds to the call site of a function. It can also be bind to where you specify it to bind to using function methods like call, apply, and bind.
function foo () {
var a = 5;
console.log(this.a);
}
var a = 2;
foo(); // 2
With ES6 arrow function, this is bound to the lexical scope.

events are not getting emitted or they not getting caught nodejs

I dont know what is the problem with my code.
// emitter.js
var EventEmitter = require('events').EventEmitter;
var util = require('util');
function Loadfun(param1, param2, db){
function __error(error, row){
if(error){
this.emit('error', error);
return true;
}
if(row.length < 1)
this.emit('failure');
}
function doSomething(){
db.query('select something', callback);
}
function callback(err, result){
if(__error(error))
return false;
else
this.emit('success', result);
}
this.doSomething = doSomething;
};
util.inherits(Loadfun,EventEmitter);
module.exports = Loadfun;
This is the emitter function.
and i am using this for some sync db works.
the following is the calling function.
var emitter = require('emitter');
router('/fetch', function(req, res){
var fetch = new emitter(param1, param2, db);
fetch.on('failure', function(){
console.log('error');
});
fetch.on('success', function(data){
console.log(JSON.stringify(data));
});
fetch.doSomething();
});
this works perfectly fine without any errors.
I tried logging the flow till the emiting of success
but the catching of the event emitting is not getting logged..
I dont understand what is the problem.. It would be nice if someone could help.
2 things that I can quickly see are:
You are passing an error if(__error(error)) which is not defined there.
You are calling this.emit in the callback function scope and it is pointing to the db.query and not the EventEmitter
You have to bind this to your callback.
Doing the following will work for you db.query('select something', callback.bind(this));
But you also have to fix your "error" mentioned in number one.
In most of your code, you are using the keyword thisin the wrong context. Every function declared with the function keyword, has its own this context, so when inside __error for example, the thisyou are referring to is not the LoadFun this and so it is not the class extending the EventEmitter class therefore does not emit anything.
You can either bind your functions when calling them, or use arrow functions, or assign to another variable, example with assigning this to another variable :
function Loadfun(param1, param2, db){
var self = this;
function __error(error, row){
if(error){
self.emit('error', error);
return true;
}
if(row.length < 1)
self.emit('failure');
}
function doSomething(){
db.query('select something', callback);
}
function callback(err, result){
if(__error(err))
return false;
else
self.emit('success', result);
}
self.doSomething = doSomething;
};

Avoiding callback hell in nodeJs / Passing variables to inner functions

Here's an example of something I'd like to simplify:
exports.generateUrl = function (req, res) {
var id = req.query.someParameter;
var query = MyMongooseModel.findOne({'id': id});
query.exec(function (err, mongooseModel) {
if(err) {
//deal with it
}
if (!mongooseModel) {
generateUrl(Id,
function (err, text, url) {
if (err) {
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
return;
}
var newMongooseModel = new AnotherMongooseModel();
newMongooseModel.id = id;
newMongooseModel.save(function (err) {
if (err) {
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
} else {
res.send({url: url, text: text});
}
});
});
} else {
//deal with already exists
}
});
};
I've seen other SO answer where they tell you to use named functions, but don't say how to deal with variable you want to pass in or use jQuery's queue. I do not have the luxury of either.
I understand that I can replace my anonymous functions with names functions, but then I would need to pass arounds variables. How would my inner function access res for instance if the function is defined elsewhere?
The core to your question is:
I understand that I can replace my anonymous functions with names functions, but then I would need to pass arounds variables. How would my inner function access res for instance if the function is defined elsewhere?
The answer is to use a function factory.
In general, this:
function x (a) {
do_something(function(){
process(a);
});
}
can be converted to this:
function x (a) {
do_something(y_maker(a)); // notice we're calling y_maker,
// not passing it in as callback
}
function y_maker (b) {
return function () {
process(b);
};
}
In the code above, y_maker is a function that generates a function (let's call that function's purpose "y"). In my own code, I use the naming convention .._maker or generate_.. to denote that I'm calling a function factory. But that's just me and the convention is in no way standard or widely adopted in the wild.
So for your code you can refactor it to:
exports.generateUrl = function (req, res) {
var id = req.query.someParameter;
var query = MyMongooseModel.findOne({'id': id});
query.exec(make_queryHandler(req,res));
};
function make_queryHandler (req, res) {
return function (err, mongooseModel) {
if(err) {
//deal with it
}
else if (!mongooseModel) {
generateUrl(Id,make_urlGeneratorHandler(req,res));
} else {
//deal with already exists
}
}}
function make_urlGeneratorHandler (req, res) {
return function (err, text, url) {
if (err) {
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
return;
}
var newMongooseModel = new AnotherMongooseModel();
newMongooseModel.id = id;
newMongooseModel.save(make_modelSaveHandler(req,res));
}}
function make_modelSaveHandler (req, res) {
return function (err) {
if (err) res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
else res.send({url: url, text: text});
}}
This flattens out the nested callbacks. As an additional benefit, you get to properly name what the function is supposed to do. Which I consider good practice.
It also has the added advantage that it is significantly faster than when using anonymous callback (either with nesting callbacks or with promises, though if you pass named functions to promise.then() instead of anonymous functions then you'll get the same speed up benefits). A previous SO question (my google-fu is failing me today) found that named functions are more than twice the speed (if I remember correctly it was more than 5 times faster) of anonymous functions in node.js.
Use promises. Using Q and mongoose-q it would give: something like that:
exports.generateUrl = function (req, res) {
var id = req.query.someParameter;
var text = "";
var query = MyMongooseModel.findOne({'id': id});
query.execQ().then(function (mongooseModel) {
if (!mongooseModel) {
return generateUrl(Id)
}).then(function (text) {
var newMongooseModel = new AnotherMongooseModel();
newMongooseModel.id = id;
text = text;
newMongooseModel.saveQ()
}).then(function (url) {
res.send({url: url, text: text});
}).fail(function(err) {
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
});
};
Named functions will be executed within the same scope that the anonymous functions are and would have access to all of variables you are currently using. This approach would make your code less nested and more readable (which is good) but would still technically be in "callback hell". The best way to avoid situations like this is to wrap your asynchronous libraries (assuming they don't already provide promises) with a promise library like Q. IMO, promises provide a much more clear picture of the execution path.
You can avoid the predicament of not knowing where variables came from by binding parameters to your named function using bind, for instance:
function handleRequest(res, err, text, url) {
if (err) {
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
return;
}
var newMongooseModel = new AnotherMongooseModel();
newMongooseModel.id = id;
newMongooseModel.save(function (err) {
if (err) {
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
} else {
res.send({url: url, text: text});
}
});
}
...
generateUrl(Id, handleRequest.bind(null, res));

Categories

Resources