NodeJS sqlite3 not returning variable (scope problem) - node.js

This code returns undefined when it should be data from the Database. It is part of a class which has the variable this.database (an sqlite3 database). What have I done wrong?
getData(chip_id) {
let user_name;
this.database.get("SELECT data FROM chips WHERE id=?",[chip_id],(err,row) => {
user_name = row.data;
});
return user_name;
}

Retrieving data from database is an asynchronous action and you should handle it with something like Promise or async function. Change your function to this.
getData(chip_id) {
return new Promise((resolve, reject) => {
let user_name;
this.database.get("SELECT data FROM chips WHERE id=?", [chip_id], (err, row) => {
if(err) {
reject(err);
}
else {
user_name = row.data;
resolve(user_name);
}
});
});
}
and in the client side:
getData(id)
.then(username => {
// the username is ready to use
})
.catch(error => {
// handle the error
});
Reading these links also helps you a lot to start:
Callbacks Vs Promises and basics of JS
Getting to know asynchronous JavaScript: Callbacks, Promises and Async/Await
Converting callbacks to promises

Related

await doesn't wait for function to end

I know this has been asked a lot but I can't seem to use the existing answers to get my code to work. I am trying to use mongojs to make a single query, then put the results in a global (relative to the scope) variable to avoid nesting multiple callbacks. However, I can't seem to get the code to wait for the query to end before continuing.
async function taskmaster() {
const db = mongojs(mongoUri.tasker);
let currentDoc;
const getTask = async function() {
db.tasks.findOne({'task_id': {'$in': [taskId, null]}}, function(err, doc) {
console.log(doc);
currentDoc = doc;
});
}
await getTask();
console.log(currentDoc);
// Code to process currentDoc below
}
No matter what I do, console.log(doc) shows a valid MongoDB document, yet console.log(currentDoc) shows "undefined". Thanks in advance!
Inside your async function, you use findOne method() in callback style, so it's totally normal that console.log(currentDoc) shows undefined, because it executes before
currentDoc = doc;
You can promisify the findOne method, to use it with async/await keyword.
I found a tutorial to promisfy a callback style function here, hope it help : https://flaviocopes.com/node-promisify/
--- EDIT ---
I rewrite your code when promising the findOne method, as suggested by O.Jones
async function taskmaster() {
const getTask = async (taskId) => {
return new Promise((resolve, reject) => {
db.tasks.findOne({'task_id': {'$in': [taskId, null]}}, function(err, doc) {
if(err) {
console.log("problem when retrieve data");
reject(err);
} else {
resolve(doc);
}
});
})
const db = mongojs(mongoUri.tasker);
const currentDoc = await getTask(taskId);
console.log(currentDoc);
}

How to send multiple objects from node backend to .hbs

I'm currently trying to send 2 objects to the front .hbs front end. However I cant seem to work out how to do this because I'm using promises.
Currently, my thinking is i perform the sql query, the country and organisation name is extracted, and then each sent to a geocoding api, returned and then squashed together in the same promises. But i'm not sure how to extract these for the render function.
Node
//route for homepage
app.get('/', (req, res) => {
let sql = "SELECT org_name, country_name from places;
let query = conn.query(sql, (err, results) => {
if (err) throw err;
const geoPromise = param => new Promise((resolve, reject) => {
geo.geocode('mapbox.places', param, function(err, geoData) {
if (err) return reject(err);
if (geoData) {
resolve(geoData.features[0])
} else {
reject('No result found');
}
});
});
const promises = results.map(result =>
Promise.all([
geoPromise(result.country_name),
geoPromise(result.org_name)
]));
Promise.all(promises).then((geoLoc, geoBus) => {
res.render('layouts/layout', {
results: JSON.stringify(geoLoc),
businesses: JSON.stringify(geoBus)
});
});
});
});
Front end call
results1 = {{{results}}}
console.log(results1.length)
business1 = {{{businesses}}}
console.log(business1.length)
Wrap your geo.geocode into a Promise
const geoPromise = param => new Promise((resolve, reject) => {
geo.geocode('mapbox.places', param, function(err, geoData) {
if (err) return reject(err);
if (geoData) {
resolve(geoData.features[0])
} else {
reject('No result found');
}
});
});
Combine both calls to geo.geocode
const promises = results.map(result =>
Promise.all([
geoPromise(result.country_name),
geoPromise(result.org_name)
]));
Call them
Promise.all(promises).then(([geoLoc, geoBus]) => {
res.render('layouts/layout', {
results: JSON.stringify(geoLoc),
businesses: JSON.stringify(geoBus)
});
});
As MadWard's answer mentions, deconstructing the argument of the callback of Promise.all is necessary since everything will be in the first argument. Make sure you check out his post for more details
Something important to recall: you will never have more than one argument in a then() callback.
Now you may ask: in the case of Promise.all(), what is this value?
Well, it is an array with all the values from the promises it awaits, in the order in which they are called.
If you do:
Promise.all([
resolveVariable1, resolveVariable2, resolveVariable3
]).then((values) => {
})
values will be [variable1, variable2, variable3], the three variables that the promises resolve to.
Your case is, however, a bit more complicated. What is gonna be returned at the end is a 2-D array containing every entry. It is an array of length results.length, and each of its element has a length of 2. The first element is the result, and the second one is the business.
Here is your snippet:
Promise.all(promises)
.then((values) => {
let results = values.map(elmt => elmt[0]);
let businesses = values.map(elmt => elmt[1]);
res.render('layouts/layout', {
results: JSON.stringify(results),
businesses: JSON.stringify(businesses)
});
})

Node.JS Mongodb query / create

I have the following Node.js javascript function:
const createOrUpdateProfile = (pProfile) => {
db.update({facebookId: pProfile.facebookId}, pProfile, {upsert:true}, function (err, profile) {
if (err) {
console.log(err);
};
console.log("profile " + profile.occupation);
return profile;
})
};
The function is called with a user profile object which is just an ordinary object with some user information. Nothing special there.
As a matter of fact the function does what i want it to do. It either finds the profile in the MongoDB database and updates it or it inserts it into the database if it's not found.
The problem is that the profile (new or updated is never returned. I guess it has something to do with the asynchronous nature of Node.
I'm already using a call back function to capture the profile but is doesn't seems to work.
what am I missing here?
EDIT:
I changed the function to look like you sugessted:
const createOrUpdateProfile = (pProfile, callback) => {
db.update({facebookId: pProfile.facebookId}, pProfile, {upsert:true}, function (err, profile) {
if (err) {
console.log(err);
};
callback(profile);
})
};
and i call it from a graphQL mutation like this:
createOrUpdateProfile: (_, { profile }) => {
Profile.createOrUpdateProfile(profile, (cbProfile) => {
// do something with the new profile
console.log(cbProfile.occupation);
})
},
but the cbProfile appears to be undefined.
Am I doing this wrong?
I will have to look into promises later. i just want to get this working first.
Kim
You can't return from an asynchronous call inside a synchronous function. Instead, you can use a callback. Like this:
const createOrUpdateProfile = (pProfile, callback) => {
db.update({facebookId: pProfile.facebookId}, pProfile, {upsert:true}, function (err, profile) {
if (err) {
console.log(err);
};
console.log("profile " + profile.occupation);
callback(profile);
})
};
Then you can call it like this:
createOrUpdateProfile(pProfile, (profile) => {
// do something with the new profile
console.log(profile);
})
If you don't want to use callbacks, then Promises are another way of achieving this functionality.

Unable to use .then in express framework

I'm new to Express framework and learning, I'm having a problem using .then. The problem is I have 2 functions and I want the first one to complete before the second to start executing. I'm exporting the modules.
var ubm = require('./userBasic');
There are 2 functions setUserData and showUserId, the showUserId must execute only after setUserData has performed its operation.
var userId = ubm.setUserData(userName,userEmail,userDOB,moment);
userId.then(ubm.showUserId(userId));
Below are the 2 functions:
module.exports = {
setUserData: function (userName,userEmail,userDOB,moment){
//Performing database activities
return userId;
}
showUserId: function (userId){
console.log(userId);
}
}
When i run it says TypeError: Cannot read property 'then' of undefined.
Like I said I'm very new and learning and was unable to figure out the solution. I did some google search and got a brief about promise, but I don't know how to implement here.
Try using promises
module.exports = {
setUserData: function(userName, userEmail, userDOB, moment) {
return new Promise(function(resolve, reject) {
//db stuff
reject(error);
resolve(userId);
});
},
showUserId: function(userId) {
console.log(userId);
};
};
So in your execution you would write
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
showUserId(data);
})
.catch((err) => {
console.log(err);
});
A couple of things to note is that in this instance you could just log data without the need for another function like
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
Whatever value you pass into resolve() will be returned as well as you pass errors into reject().

Way to use mongodb's find cursor's next/each functions as a promise after promisifying mongodb with Bluebird

The node mongodb docs specify to use next/each for large number of documents so as to not have everything loaded onto memory if we were to use toArray.
So, i thought my sample code should work as is. But it just returns one document.
What should be the correct way to deal with this problem?
This is my code sample :
var findAsync = function (collection,query) {
return mongodb.MongoClient.connectAsync(mongodbServerString)
.then(function (db) {
return [db.collection(collection).find(query), db];
});
};
findAsync("UserProfile",{})
.spread(function (cursor,db) {
return [cursor.project({Email:true}),db];
})
.spread(function (cursor, db) {
return cursor.eachAsync().then(function (doc) {
console.log(doc);
}).catch(function () {
db.close();
});
});
Promises represent singular values. Promises are basically like function returns, since a function cannot return multiple values - it wouldn't make sense to convert each to a promise returning function.
What you can do is either convert it to an Observable returning function and then use .forEach on that to get a promise back for the completion of the sequence or you can implement something similar manually:
function each(cursor, fn) {
return new Promise((resolve, reject) => {
cursor.forEach((err, data) => {
if(err) {
cursor.close();
return reject(err);
}
try { fn(data); } catch(e) { cursor.close(); reject(e); }
}, err => { { // finished callback
if(err) reject(err);
else resolve();
});
});
}
Which would let you write:
each(cursor, doc => console.log(doc)).then(...).catch(...)
Also note that Mongo connections are persistent, you're supposed to connect once when the server starts and then keep the connection open for as long as the server is run.

Resources