I'm trying to set a class member variable from a callback of a function I'm calling from the class constructor.
To be a bit more specific: I need to set the connection ID in the Connection class constructor based on the Redis INCR result (each client has a 'global' connection ID so I can have multiple nodes).
Here's the code.
class Connection {
constructor() {
client.incr('conn_id', (err, reply) => {
this.connID = reply;
});
}
}
var lovely = new Connection();
console.log(`lovely connID is ${ lovely.connID }`);
This is the result: lovely connID is undefined
It seems that client.incr('conn_id' ....) is async , which means the callback will be invoked after your code run .
So
console.log(lovely connID is ${ lovely.connID }); will be called before the callback
(err, reply) => {
self.connID = reply;
}
which is similar to this :
class Connection{
constructor(){
self=this;
setTimeout( function(){self.client='somevalue';
console.log('value1');}, 10)
}
}
var a = new Connection();
console.log(a.client);
running this will result
undefined
value1
As others here have mentioned, the issue seems to be that client.incr is asynchronous and your code does not wait for it to resolve before accessing the properties. To remedy this issue, you could try passing in an onReady callback to Connection to enssure the properties will be there before trying to access them. Something along these lines:
'use strict';
// mock client.incr
var client = { incr: (id, callback) => {
setTimeout(() => callback(null, 'Hello World'), 0)
}};
class Connection {
// receive an "onReady" function
constructor(onReady) {
client.incr('conn_id', (err, reply) => {
this.connID = reply;
// call "onReady" function, and pass it the class
if (typeof onReady === 'function') onReady(this)
});
}
}
new Connection(lovely => { console.log(lovely.connID) })
I hope that helps!
In general putting heavy initialization logic in a constructor, especially if it's asynchronous, is not a good idea. As you've found, the constructor has no way to return the information about when the initialization is finished. An alternative is to create a promise for when the connection is ready. Then, in your outside code, you can hang a then off the property to specify the code you want to trun when it's ready.
class Connection {
constructor() {
this.connId = new Promise((resolve, reject) =>
client.incr('conn_id', (err, reply) => {
if (err) return reject(err);
resolve(reply);
});
}
}
var lovely = new Connection();
lovely.connId . then(connId => console.log(`lovely connID is ${ connID }`);
Related
I have two classes:
Socket baseclass - Which handles basic operations for a socket connection
Socket subclass - Which should apply specific operations i.e. when a message is received
In my application, I have multiple socket connections that I may open - Hence why I don't want to repeat all the logic for opening and closing my socket connection.
Similarly, when I receive a message on one of the connections I may handle the messages differently, depending on what connection I have opened.
Therefore, I came up with the following solution:
class SocketBase {
constructor(messageHandler, errorHandler) {
this.messageHandler = messageHandler
this.errorHandler = errorHandler
}
openSocket = async () => {
//Logic for opening up socket
//Subscribe to socket events
.subscribe(
(x) => this.messageHandler(x),
(err) => this.errorHandler(err),
() => console.log("Observer got a complete notification"),
)
}
}
As you can see, I want to pass two callback functions to my above super-class - Respectively one for handling incoming messages, and another for handling eventual error events.
My sub-class looks as follows:
class Sub extends SocketBase {
constructor(link) {
super(this.messageHandler, this.errorHandler)
}
errorHandler = (err) => {
//some logic
}
messageHandler = (message) => {
//some logic
}
}
Since the incoming messages are events, I thought passing a callback function to the super-class made sense. However, I realize that my sonarlint is complaining.
i.e.
'this' is not allowed before 'super()'.sonarlint(javascript:S3854)
It is however possible for me to define the callback function in my constructor arguments as follows:
super(function(message){console.log(message)}, function(error){console.log(error)})
Ofcause this is not what I want, for obvious reasons the above solution may get very messy.
Another solution would be for me to construct my base-class, but that would give me some undesired issues as well.. For example, I would have to do as follows:
this.socketBase = new SocketBase(this.messageHandler, this.errorHandler)
//Open socket (Not pretty)
openSocket = () => {
this.socketBase.openSocket()
}
Conclusively, I want to understand how I should approach this problem?
With your code example, there is no need to pass the functions, at all:
class SocketBase {
openSocket = async () => {
//Logic for opening up socket
//Subscribe to socket events
.subscribe(
(x) => this.messageHandler(x),
(err) => this.errorHandler(err),
() => console.log("Observer got a complete notification"),
)
}
}
class Sub extends SocketBase {
constructor(link) {
super();
// do whatever you need to do with `link` parameter
}
errorHandler = (err) => {
//some logic
}
messageHandler = (message) => {
//some logic
}
}
If for whatever strange reason you need the passing to the super class, here you go.
You can define constants and pass those to super, and by calling super, you already assign those to your instance this.
class Sub extends SocketBase {
constructor(link) {
const errorHandler = (err) => {
// errorHandler Code here
}
const messageHandler = (msg) => {
// messageHandler Code here
}
super(messageHandler, errorHandler)
}
}
You can also work with closures here, but in that case remember to use regular functions and bind them in the superclass:
function errorHandler(err) {
// errorHandler Code here
}
function messageHandler(msg) {
// messageHandler Code here
}
class Sub extends SocketBase {
constructor(link) {
super(messageHandler, errorHandler);
}
}
class SocketBase {
constructor(messageHandler, errorHandler) {
this.messageHandler = messageHandler.bind(this);
this.errorHandler = errorHandler.bind(this);
}
}
Please help trying to get this function to return value back to my main process. currently everything shows in the console however if I return the object its blank or undefined
const GetSelectDeviceFromDB = () => {
db.all("SELECT * FROM device_list", function (err, rows) {
rows.forEach(function (row) {
console.log(row.device);
return row.device;
});
});
};
module.exports = { GetSelectDeviceFromDB };
OUPUTS:
console.log =. { device1, device2 }
return = undefined and if I add the return to the beginning of the sql statement I get {}
Since all() method is asynchronous and it is using a callback, you can turn your method into a method like this:
const GetSelectDeviceFromDB = () => new Promise((resolve, reject) => {
db.all('SELECT * FROM device_list', (err, rows) => {
if (err) {
reject(err);
}
const devices = rows.map((row) => row.device);
resolve(devices);
});
});
It will return a Promise, so you can call it like this:
GetSelectDeviceFromDB().then(devices => { ... })
Returning from forEach isn't a good idea, returning from another object's method (db.all in you case) isn't either. You need to return exactly in the first scope of the lambda function, somewhere outside of db.all(...). But in this case it's an async method, so you should make your whole function async or a Promise
I am a beginner in javascript and am now trying to understand the subject of classes. I've defined the following class. Now I would like to use the result outside of this in a variable.
The class looks like this:
class Maad{
constructor(name, NumWeek, NumMonth){
this.name =name;
this.NumWeek = NumWeek;
this.NumMonth = NumMonth;
}
queryMaad(){
const mongodb =require('mongodb');
const client = require('mongodb').MongoClient;
const url= 'mongodb://localhost:27017/vertrieb';
client.connect(url,(error, db) =>{
if(!error){
console.log("month_log steht")
};
let col = db.collection("umsatz5");
col.aggregate([{'$match': {'AD': this.name}}, {'$match': {'Kalenderwoche':this.NumWeek}}, {'$count': 'procjetnumber'}],function(err, result){
if (err) {
console.error("Error calling", err);
}
console.log(result[0].projectnumber);
result[0].projectnumber;
})
db.close();
});
}
}
My request is:
let ma_1 = new Maad("Hans Wurst", NumWeek);
ma_1.queryMaad();
How can I save the result (the number of projects) in a variable to use it outside of the class? Thanks for your help.
In general, you would assign it basically the way that you assign anything:
const ma_1 = new Maad("Hans Wurst", NumWeek);
const myVar = ma_1.queryMaad();
However your method is a void method which doesn't return anything, so you need to edit your class if you want to get the number of projects.
Returning something from the function is harder than it sounds because MongoClient.connect is a void method which uses callbacks rather than returning a Promise of a response. Honestly I would recommend using a library like mongoose. But it is possible to make the method asynchronous ourselves by returning a new Promise which we resolve or reject based on the callbacks.
class Maad {
constructor(name, NumWeek, NumMonth) {
this.name = name;
this.NumWeek = NumWeek;
this.NumMonth = NumMonth;
}
async queryMaad() {
const url = "mongodb://localhost:27017/vertrieb";
return new Promise((resolve, reject) => {
MongoClient.connect(url, (error, db) => {
if (error) {
reject(error);
}
console.log("month_log steht");
let col = db.collection("umsatz5");
col.aggregate(
[
{ $match: { AD: this.name } },
{ $match: { Kalenderwoche: this.NumWeek } },
{ $count: "procjetnumber" }
],
function (err, result) {
if (err) {
reject(err);
}
resolve(result);
}
);
db.close();
});
});
}
}
Now queryMaad is an async method, one which returns a Promise. That Promise will resolve to the result of col.aggregate on success (you could also resolve to result[0].projectnumber). The promise will reject if there is an error in the connect or col.aggregate methods.
You now get your value like so (probably inside of a function which is itself async):
const result = await ma_1.queryMaad();
You can catch rejection errors here, or allow them to be thrown and catch them higher up.
try {
const result = await ma_1.queryMaad();
} catch (error) {
// console.error or whatever
}
I am using a library called node-geocoder in express js which has the following code:
var NodeGeocoder = require('node-geocoder');
var options = {
provider: 'google',
// Optional depending on the providers
httpAdapter: 'https', // Default
apiKey: 'YOUR_API_KEY', // for Mapquest, OpenCage, Google Premier
formatter: null // 'gpx', 'string', ...
};
var geocoder = NodeGeocoder(options);
// Using callback
geocoder.geocode('29 champs elysée paris', function(err, res) {
console.log(res);
});
The response variable(res) in the geocode method's callback function holds an object with location properties such as latitutde and longitude. The link for this package is here
I was wondering if there was a way to use that response variable outside of the callback function in the geocode method. I need to pull the latitude and longitude properties and I don't want to keep the rest of the code within that callback function.
As a noob I tried just returning the object and storing it in a variable like so:
var object = geocoder.geocode('29 champs elysée paris', function(err, res) {
return res;
});
This doesn't work since it's in the callback and not returned in the actual geocode method.
Not directly, but there are a couple of options to get closer to that.
One would be to have your callback be a defined function and use that as the callback:
const doSomething = (err, res) => { /* do something */ }
geocoder.geocode('abc', doSomething);
Not really much different, but can make it a little cleaner.
You can also "promisify" the function to have it return a Promise. Something like this would do the trick:
const geocodePromise = (path) => new Promise((resolve, reject) => {
geocoder.geocode(path, (err, res) => err ? reject(err) : resolve(res));
});
geocodePromise('abc')
.then(res => { /* do something */ })
.catch(err => { /* do something */ });
Finally, if you are using Babel for transpiling (or Node version 7.6.0 or higher, which has it natively), you can use async and await. Using the same promisified version of the function as above, you'd have to wrap your main code in an async function. I generally use a self-calling anonymous function for that:
(async () => {
try {
const res = await geocodePromise(path);
/* do something with res */
} catch (err) {
/* do something with err */
}
})();
With this, you get the closest to what you want, but you'll still have to wrap your main code up in a function because you can't await at the top level.
I don't want to keep the rest of the code within that callback function.
The code doesn't have to be in the callback, it just has to be called from the callback.
So you write your function (or functions) as normal:
function handleInfo(info) {
doSomethingWithInfo(info);
doSomethignElse(info);
// ...
}
// ...
...and then call those functions when you have the data:
geocoder.geocode('29 champs elysée paris', function(err, info) {
if (err) {
// Handle error
} else {
handleInfo(info);
}
});
You can use the response of your function outside the callback by calling another function.
geocoder.geocode('29 champs elysée paris', function(err, res) {
if(!err){
// call a function if there is no error
console.log(res);
myFunction();
}
});
function myFunction(){
//do your processing here
}
geocode can return promise already, no need to re-wrap it. If you don't care about the error and just want to grab the response's location data. You can do this
var locationData = geocoder.geocode('29 champs elysée paris').then( function(res){
return res;
});
I am still struggling with the nested callback structure of Node.js. I have looked as async, co and other methods, but they do not seem to help.
What is the best practice on how to code, e.g.
var db = MongoClient.connect(url, callback1 () {
if (auth) }
db.authenticate(user, pswd, callback2 () {
--- should continue with db.collection.find (authenticated)
)
--- should continue with db.collection.find (non-authenticated)
}
So the question ist: How should I code this sequence to be able to execute the db calls following db.connect or db.authenticate (and both callbacks are completed)? The only way I can think of is to have the following db-calls in a separate function and call this function in both callback routines. Not really elegant ...
If what you are confused by is how to put optional conditions before a callback, using async you can do:
var async = require('async');
var db = MongoClient.connect(url, () => {
async.series([
(callback) => {
//Branching
if(auth) {
// Do conditional execution
db.authenticate(user, pswd, () => {
callback();
});
} else {
// Skip to the next step
callback();
}
},
(callback) => {
// Whatever happened in the previous function, we get here and can call the db
db.collection.find();
}
]);
});
I'm not sure that I fully understand what you are asking, by the way, if you want to run a callback depending on some conditions (eg: requires or not authentication)... you can use promises:
var db = MongoClient.connect(url, callback1 () {
if (auth) }
db.authenticate(user, pswd, callback2 () {
--- should continue with db.collection.find (authenticated)
)
--- should continue with db.collection.find (non-authenticated)
}
var MongoClientConnect = (url, username, password) => new Promise((resolve, reject) => {
var db = MongoClient
.connect(url, () => {
if(!requiresAuthentication) {
return resolve(db);
}
db.authenticate(username, password, () => {
//check if login success and
resolve(db);
});
})
;
});
// THEN
MongoClientConnect()
.then(db => db.collection.find("bla bla bla"))
;