JavaScript - var self = this; self is undefined [duplicate] - javascript

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 6 years ago.
so here is a situation that i have. I have a constructor function with many properties and methods. here are the properties:
var Brain = function(){
this.util = require('util');
this.fs = require('fs');
this.assert = require('assert');
this.Sequelize = require('sequelize');
this.JsonField = require('sequelize-json');
this.bcrypt = require('bcrypt-nodejs');
this.bodyParser = require('body-parser');
this.fileUpload = require('express-fileupload');
// Custom Modules
this.ModelsModule = require('./models');
this.TombModule = require('./tomb');
}
a few of the methods are defined to call a given callback and pass data to it, for example:
Brain.prototype.db_read = function(request, response, data, callback) {
var self = this;
self.ModelsModule[data.Model].findOne(data.where)
.then(function(obj) {
// console.log(obj);
callback(request, response, obj);
})
.catch(function(error){
console.log(error);
});
}
Here is the thing - the callback function is always going to be another method of the same constructor function. here is another method that would be a callback:
Brain.prototype.login = function(request, response, user) {
var self = this;
// console.log('form-data --- ', request.body);
if(user == null || user == undefined) {
console.log('Account Load: Failed');
return response.render( self.TombModule.pages['login'], {error: 'Invalid Credentials'} );
}
else {
// console.log(user.dataValues);
if( self.bcrypt.compareSync(request.body.pswrd, user.dataValues.pswrd) == false ) {
console.log('Account Load: Failed');
return response.render( self.TombModule.pages['login'], {error: 'Invalid Credentials'} );
}
console.log('Account Load: Successful');
request.session.you = {
id: user.dataValues.id,
f_name: user.dataValues.f_name,
m_initial: user.dataValues.m_initial,
l_name: user.dataValues.l_name,
icon: user.dataValues.icon,
background: user.dataValues.background,
email: user.dataValues.email,
phone: user.dataValues.phone,
fax: user.dataValues.fax,
uv: user.dataValues.uniqueValue
};
return response.redirect('/home');
}
}
In my app.js a new instance of Brain is created.
const RoutinesModule = require('./routines');
const brain = new RoutinesModule.Brain();
The Entire chain of events starts from an express POST route:
app.post('/login', function(request, response){
var data = {
Model: 'Users',
where: {where: {email: request.body.email}}
}
brain.db_read(request, response, data, brain.login);
});
Notice that the callback, the last parameter, for Brain.db_read is Brain.login, which is another method of the same constructor. here is where the problem is happening.
When POST requests to /login hits the app.js, it's going to query the database and give the results to any given function to handle it, in this case Brain.login
inside of Brain.db_read(), var self = this; works. it points to itself, the instance of the Brain. however, when it calls the callback which is Brain.login, the statement inside of Brain.login, var self = this; does not work. it results in undefined, causing an error.
Why is that happening? why is var self = this; inside of the Brain.login resulting in undefined?
Ultimately what i am trying to do is create a set of main functions to handle database operations(CRUD) instead of doing database operations inside of each individual function, which is/could be an indefinite amount.
I could easily just require that module in that function definition but i would strongly prefer to access its property for dynamic, scalable, and efficiency means.
Thanks!

You're passing the method, brain.login, as a function that has no owner. You're just passing the function reference. You need to do brain.login.bind(brain).

Related

Use of indexedDB returns 'undefined'

I'm trying to use indexedDB.
Some parts of my code works.
In the following example, the first function adds server in my DB, however in Chrome debug console there is an undefined message not related to any line. The server is already added though.
The second function puts records in an array, there is also an undefined message not related to any line.
If I do a console.log(servers); just before return servers; I can see the array content, however if I call the function somewhere else in my code, the returned object is undefined.
var dbName = 'myDBname',
dbServersStoreName = 'servers',
dbVersion = 1,
openDBforCreation = indexedDB.open(dbName, dbVersion);
openDBforCreation.onupgradeneeded = function(e) {
var db = e.target.result;
var objStore = db.createObjectStore(dbServersStoreName, { keyPath: "alias"
});
var index = objStore.createIndex("serversAlias", ["alias"]);
};
function addServerInDB(serverAlias,serverAddress,user,pwd){
var myDB = indexedDB.open(dbName, dbVersion);
myDB.onerror = function() {
var notification = document.querySelector('.mdl-js-snackbar');
notification.MaterialSnackbar.showSnackbar(
{message: 'Error while trying to access internal database'});
}
myDB.onsuccess = function(e) {
var db = e.target.result,
request = db.transaction([dbServersStoreName],
"readwrite").objectStore("servers")
.put({alias:''+serverAlias+'',
address:''+serverAddress+'', login:''+user+'',
passwd:''+pwd+''});
request.onsuccess = function(){
var notification = document.querySelector('.mdl-js-snackbar');
notification.MaterialSnackbar.showSnackbar(
{message: 'Server added'});
}
}
};
function listServersInDB(){
var myDB= indexedDB.open(dbName, dbVersion);
myDB.onerror = function() {
var notification = document.querySelector('.mdl-js-snackbar');
notification.MaterialSnackbar.showSnackbar(
{message: 'Error while trying to access internal database'});
}
myDB.onsuccess = function(e) {
var servers = new Array(),
db = e.target.result,
request = db.transaction(["servers"], "readwrite")
.objectStore("servers")
.openCursor();
request.onsuccess = function(e){
var cursor = e.target.result;
if(cursor){
servers.push(cursor.value);
cursor.continue();
}
return servers;
}
}
};
I do not understand where this undefined comes from and if that is why the listServersInDB() function doesn't work.
You need to learn more about how to write asynchronous Javascript. There are too many errors in your code to even begin reasoning about the problem.
Briefly, don't do this:
function open() {
var openDatabaseRequest = ...;
}
openDatabaseRequest.foo = ...;
Instead, do this:
function open() {
var openDatabaseRequest = ...;
openDatabaseRequest.foo = ...;
}
Next, you don't need to try and open the same database multiple times. Why are you calling indexedDB.open twice? You can open a database to both install it and to start using it immediately. All using the same connection.
Next, I'd advise you don't name the database open request as 'myDB'. This is misleading. This is an IDBRequest object, and more specifically, an IDBOpenRequest object. A request isn't a database.
Next, you cannot return the servers array from the request.onsuccess at the end. For one this returns to nowhere and might be source of undefined. Two this returns every single time the cursor is advanced, so it makes no sense at all to return return servers multiple times. Three is that this returns too early, because it cannot return until all servers enumerated. To properly return you need to wait until all servers listed. This means using an asynchronous code pattern. For example, here is how you would do it with a callback:
function listServers(db, callbackFunction) {
var servers = [];
var tx = db.transaction(...);
var store = tx.objectStore(...);
var request = store.openCursor();
request.onsuccess = function() {
var cursor = request.result;
if(cursor) {
servers.push(cursor.value);
cursor.continue();
}
};
tx.oncomplete = function() {
callbackFunction(servers);
};
return 'Requested servers to be loaded ... eventually callback will happen';
}
function connectAndList() {
var request = indexedDB.open(...);
request.onsuccess = function() {
var db = request.result;
listServers(db, onServersListed);
};
}
function onServersListed(servers) {
console.log('Loaded servers array from db:', servers);
}
When you call a function that does not return a value, it returns undefined. All functions in JavaScript return undefined unless you explicitly return something else.
When you call a function from the devtools console, and that function returns undefined, then the console prints out '-> undefined'. This is an ordinary aspect of using the console.
If you want to get a function that returns the list of servers as an array, well, you cannot. The only way to do that in a pretend sort of way, is to use an 'async' function, together with promises.
async function getServers() {
var db = await new Promise(resolve => {
var request = indexedDB.open(...);
request.onsuccess = () => resolve(request.result);
});
var servers = await new Promise(resolve => {
var tx = db.transaction(...);
var request = tx.objectStore(...).getAll();
request.onsuccess = () => resolve(request.result);
});
return servers;
}
One more edit, if you want to call this from the console, use await getServers();. If you do not use the top-level await in console, then you will get the typical return value of async function which is a Promise object. To turn a promise into its return value you must await it.
Clear and helpfull explanations, Thank you.
I open database multiple times beacause the first time is for checking if DB needs an upgrade and doing something if needed. I'll add 'db.close()' in each functions.
Then, I tried your exemple and the result is the same:
console.log('Loaded servers array from db:', servers); works
but return servers; Don't work.
And in console there is already an undefined without related line :
Screenshot

Property cannot be read after wrapping function

var sync = Meteor.wrapAsync(connection.query);
var rows = sync(selectMailgunChecked);
var emails = rows.map(function(a) {
return a.email
});
var from = 'sample#email.com';
emails.forEach(function(entry) {
mailgunSend( entry, from, contentHTML, contentRAW, subject, tags);
});
Code above (wrapped function of connection.query from node-mysql use in Meteor app) gives me an arror:
Cannot read property 'typeCast' of undefined
It is somehow related to sync(selectMailgunChecked) and external library (from node-mysql) Connection.js:
Connection.prototype.query = function query(sql, values, cb) {
var query = Connection.createQuery(sql, values, cb);
query._connection = this;
if (!(typeof sql == 'object' && 'typeCast' in sql)) {
query.typeCast = this.config.typeCast;
}
if (query.sql) {
query.sql = this.format(query.sql, query.values);
}
this._implyConnect();
return this._protocol._enqueue(query);
};
Every variable in my code is defined and sucessfully passed. What Can be wrong here?
this.config in this line
query.typeCast = this.config.typeCast;
is undefined.
You have to define the context (this) in which the wrap async function connection.query is executed by passing it as the second parameter
var sync = Meteor.wrapAsync(connection.query, connection);

Issue with prototype inheritance in Javascript (Node.js)

I have two files: BaseController.js and EventRecordController.js. EventRecord needs to inherit a few methods from BaseController.
BaseController
var Q = require('q'),
util = require('../util');
exports.BaseController = function(req, res) {
this.req = res;
this.res = res;
this.fields = {};
this.error = {
code: 200,
message: 'BAD REQUEST: The parameters provided were invalid. See response body for error messages.',
specific_message: ''
};
};
// Utility method to handle returning errors that are thrown.
exports.BaseController.prototype.handle_errors = function(error) {
if(this.error.code === 500) {
util.internal_error(this.res, this.response_type);
} else {
var response = util.build_error_response(this.response_type, this.error.code, this.error.message, this.error.specific_message);
util.send_response(this.res, this.response_type, this.error.code, response);
}
};
// Check to see if a user is authenticated and whether they are using a correct response type.
exports.BaseController.prototype.validate_response_type_and_authenticate = function() {
var deferred = Q.defer();
util.validate_response_type_and_authenticate(this.req, this.res, function(auth_data, response_type) {
this.auth_data = auth_data;
this.company_user_uid = this.auth_data.data.company.uid;
this.response_type = response_type;
this.v3_token = this.auth_data.data.token;
deferred.resolve();
});
return deferred.promise;
};
EventRecordController
var base_controller = require("./BaseController"),
Q = require('q'),
util = require('../util'),
validator = require('validator');
exports.EventRecordController = function(req, res) {
function EventRecord(req, res) {
base_controller.BaseController.apply(this, arguments);
}
// Inherit from BaseController, then fix constructor.
EventRecord.prototype = new base_controller.BaseController();
EventRecord.prototype.constructor = EventRecord;
EventRecord.run = function() {
console.log(this.error);
};
return EventRecord;
};
When I run the following code, this.error logs as undefined from within the run() method.
var event_record_controller = require("./controllers/EventRecordController"),
util = require('./util'),
validator = require('validator');
exports.record = function(req, res) {
var controller = new event_record_controller.EventRecordController(req, res);
controller.run();
};
I think I'm missing something obvious here, but my experience with prototype based inheritance is limited.
this.error is undefined because run is being called directly on the constructor, which doesn't have an error, rather than one of its instances.
Methods that are attached directly to the constructor aren't inherited. For that, they should be attached to the prototype:
// "static" method available only through the constructor itself
EventRecord.run = function() {
console.log(this.error);
};
// "class" method inherited by instances of `EventRecord`
EventRecord.prototype.run = function () {
console.log(this.error);
};
But, you also don't yet have an instance of EventRecord to call .run() on.
When a constructor returns an object, the instance that was created by using new will be discarded. So, calling new EventRecordController() is just returning the function EventRecord.
var controller = new event_record_controller.EventRecordController(req, res);
console.log(typeof controller); // function
console.log(controller.name); // "EventRecord"
controller = new controller(req, res);
console.log(typeof controller); // object
You could revise EventRecordController to return an instance of EventRecord:
// ...
return new EventRecord(req, res);
Though, you might consider consolidating the 2 constructors rather than having one generate the other:
exports.EventRecordController = function(req, res) {
base_controller.BaseController.apply(this, arguments);
};
util.inherits(exports.EventRecordController, base_controller.BaseController);
exports.EventRecordController.prototype.run = function () {
console.log(this.error);
};

Suggest a way around this callback not letting my constructor set values

I have a user
user = new User('name','pwd');
which should contain a valid property
var User = module.exports = function User(username, password){
db.query('MATCH (user) WHERE user.username={username} RETURN user',
{username:username},
function(err,result){ // << The callback
if(err) return console.log('error: '+err);
if(result[0])
this.valid=true; // << this.value
if(result[0].user.data.hash == crypto.createHash('sha256').update(password+result[0].user.data.salt,'utf8').digest('hex'))
this.authenticated=true;
}
);
}
But since this.valid is called from within a callback of the db.query, it doesn't execute before the new object is already returned without this.valid.
What can I do?
See How to access the correct this / context inside a callback?, regarding this.
However, the bigger problem, as you already mentioned, is rather that db.query is asynchronous, i.e. even if the object is created, you cannot immediately use it because the values won't be set yet. Instead, you should create a factory function, which makes the db.query call, creates the User instance and pass it to a callback. This way you can be sure that the User object is fully populated.
Example:
function User(valid, authenticated) {
this.valid = valid;
this.authenticated = authenticated;
}
function createUser(username, password, callback) {
db.query(..., ..., function(err, result) {
var valid = ...;
var authenticated = ...;
callback(new User(valid, authenticated));
});
}
createUser('foo', 'bar', function(user) {
// do something with user
});
you can do this to have access to your this context object:
var User = module.exports = function User(username, password){
var $this = this;
db.query('MATCH (user) WHERE user.username={username} RETURN user',
{username:username},
function(err,result){ // << The callback
if(err) return console.log('error: '+err);
if(result[0])
$this.valid=true; // << this.value
if(result[0].user.data.hash == crypto.createHash('sha256').update(password+result[0].user.data.salt,'utf8').digest('hex'))
$this.authenticated=true;
}
);
}

Mongoosejs: how to pass node validator object to the callback function of a query

I'm struggling for a while trying to pass variables/functions to a mongoose query callback function.
What I'm trying to do is:
EDIT: title changed and example redefined
Validator.protoptype.customCheck = function(model, path) {
var self = this // validator object {msg: 'msg', error: ...};
model.find({name: 'john'}, function(err, res) {
if (res.length === 0) {
self.error( self.msg ) // msg hasn't the expected value.
// As I have some other validators after this callback gets executed
// the context is different. don't know how to pass the right context
} else {
// something else ...
}
}
}

Categories