Returning value from async function node.js - javascript

I know it is the most frquently asked question in javascript regarding asynchronous behaviour of functions. Still I am not able to figure out a working solution for my use case.
What I am trying to do is lookup the redis cache using a key. I am using the exists function to check the key is present or not. If present then i'll return from cache if not then i'll set the key and then make a db call using this key as a db param. It seems very simple but no matter what I do i'm unable to return the value from the cache or the DB. If I make these calls outside the exist function then it works as the resolver function(graphql resolver) is an async function. This resolver functions expects a return value.
So here is the code by which i'm unable to retun the value in any scenario:-
empId: async(obj, params, ctx, resolverInfo) => {
await client.exists(obj.empId, async function(err, reply) {
if (reply == 1) {
return await getAsync(obj.empId).then(res => {
console.log(res);
return res;
})
} else {
return await db.one('SELECT * FROM iuidtest WHERE empid = $1', [obj.empId])
.then(iuidtest => {
console.log(iuidtest.empid);
return iuidtest.empid;
})
}
});
const doSomethingWith = empid => {
console.log("empid = ", empid);
}
I am getting the correct values in console but am unable to return. However if I directly make these calls in my resolver function i.e. outside of the redis exists function I am able to return the value.
empId: async(obj, params, ctx, resolverInfo) => {
return await getAsync(obj.empId).then(res => {
console.log(res);
return res;
This way I am able to return the value from the resolver function. It would be really of great help if anybody can provide the working code for this instead of other links regarding how to return from async function using callbacks and promises. Here is another post reg the same. :-
Redis async library does not have function that are in redis library for node.js
Thanks in advance!

If client.exists returns promise, the same code can be written as below:
empId: async (obj, params, ctx, resolverInfo) => {
const exists = await client.exists(obj.empId);
if (exists === 1) {
return getAsync(obj.empId);
}
return await db.one('SELECT * FROM iuidtest WHERE empid = $1', [obj.empId])
.then(iuidtest => {
return iuidtest.empid;
});
}
If client.exists only accepts callback, then the code can be written as:
empId: async (obj, params, ctx, resolverInfo) => {
async function empIdExists(empId) {
return new Promise(function resolver(resolve, reject) {
client.exists(obj.empId, function(err, reply) {
if (err) {
reject(err);
return;
}
if (reply == 1) {
resolve(1);
return;
} else {
resolve(0);
return;
}
})
});
}
const exists = await empIdExists(obj.empId);
if (exists === 1) {
return getAsync(obj.empId);
}
return await db.one('SELECT * FROM iuidtest WHERE empid = $1', [obj.empId])
.then(iuidtest => {
return iuidtest.empid;
});
}
In the second version, notice that I have wrapped the client.exists call into an async function & called using await keyword.

Related

Javascript - Convert function that have a least two callbacks style to promise style

I moving from callback style to promise style to avoid the callback that made me hard to maintain the code. I use util module to import and convert the callback function with util.promisify(), but the question is if the function have at least two callbacks. How the function will return in promise form, then how can I manage the code to get the promise from two conditional callback.
Callback style example.
loginAccount = async ({email,password},success,unsuccess) => {
this.db.query(`SELECT id, password FROM account WHERE email = ?`,[email],(err,result)=>{
if (err) throw err
if (result.length === 0) {
unsuccess("ไม่มีบัญชีนี้อยู่ในระบบ ตรวจสอบอีเมลว่าถูกต้องหรือไม่")
return
}
const account = result[0]
bcrypt.compare(password,account.password,(error,response)=>{
if (err) throw err
if (!(response)) {
unsuccess("รหัสผ่านไม่ถูกต้อง")
return
}
this.getUser(account.id,success,unsuccess)
})
})
}
I can convert into promise form like this, but I don't know how to return success or unsuccess.
loginAccount = async ({email,password},success,unsuccess) => {
const result = await this.query(`SELECT id, password FROM account WHERE email = ?`,email)
if (result.length > 0) {
const account = result[0]
const response = await this.compareHash(password,account.password)
if (response) {
return this.getUser(account.id)
}
else {
// return unsuccess
// Wrong password
}
}
else {
// return unsuccess
// No account in the database
}
}
another example of callback style and promise style.
registerAccount = async ({firstname,lastname,email,password},success,unsuccess) => {
this.hasAccount(email,(exist)=>{
if (exist === true) {
unsuccess("บัญชีนี้เคยลงทะเบียนไว้แล้ว")
return
}
bcrypt.hash(password,this.saltRound,(err,hash)=>{
if (err) throw err
this.db.query(`INSERT INTO account (firstname,lastname,email,password) VALUES(?,?,?,?);
SET #user_id = LAST_INSERT_ID(); INSERT IGNORE INTO role (id) VALUES (#user_id);
INSERT INTO student_data (id) VALUES (#user_id);
INSERT INTO profile_image (id) VALUES (#user_id);`,
[firstname,lastname,email,hash],
(err,result)=>{
if (err) throw err
success()
})
})
})
}
registerAccount = async ({firstname,lastname,email,password},success,unsuccess) => {
const exist = await this.hasAccount(email)
if (exist) {
// unsuccess function return
// account is already exists.
return
}
const hash = await this.generateHash(password,this.saltRound)
const registerQuery = `INSERT INTO account (firstname,lastname,email,password) VALUES(?,?,?,?);
SET #user_id = LAST_INSERT_ID(); INSERT IGNORE INTO role (id) VALUES (#user_id);
INSERT INTO student_data (id) VALUES (#user_id);
INSERT INTO profile_image (id) VALUES (#user_id);`
const result = await this.query(registerQuery,[firstname,lastname,email,hash])
// success function
}
this.query, this.compareHash and this.generateHash, I'm using the library called util.promisify() to get promise function.
this.query come from mysql query function, this.compareHash and this.generateHash both are come from bcrypt.compare and brcypt.hash
this.compareHash will compare the hash by using plain password and hash password.
this.generataHash will generate the hash by using plain password and salt round to return a hash password.
this.getUser function.
getUser = async (id) => {
const result = await this.query(`SELECT * FROM profile_view WHERE id = ?`,id)
return result[0]
}

javascript function will not return value or object

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

using async with mysql

So i got the following code. I want to check if Name already exists in my database. If not I want to insert it. And it basically works with my code. My only problem is, that the last bit of code where it checks whether "success" is true gets executed before it even checked the database. (when using a Name that doesn't exist in the database my console is like this:
Name is already taken <--- this piece of code should be executed last. Then that would not appear
No Duplicate, insert it
starting insert function
I've heard of async and await but I just could not figure it out. I could really need some help.
Here is my Code:
app.post("/api/createProject", (req, res) => {
var success = false;
// console.log(req.body);
const Name = req.body.Name;
const Status = req.body.Status;
const Deadline = req.body.Deadline;
const EstimatedHours = req.body.EstimatedHours;
const BudgetHours = req.body.BudgetHours;
const BudgetCost = req.body.BudgetCost;
const Description = req.body.Description;
// check if Projct Name is already taken
const SEARCH_NAME = `SELECT ID FROM projects WHERE Name = '${Name}'`;
db.query(SEARCH_NAME, (err, results) => {
if (err) {
return err;
} else {
if ((results.length < 1)) {
console.log("No Duplicate, insert it")
insertDatabase();
}
}
});
// will only get executed when no Errors got detected above (Name is not already taken)
function insertDatabase() {
console.log("starting insert function");
const CREATE_NEW_PROJECT = "INSERT INTO projects (Name, Status, Deadline, EstimatedHours, BudgetHours, BudgetCost, Description) VALUES (?,?,?,?,?,?,?)";
db.query(
CREATE_NEW_PROJECT,
[
Name,
Status,
Deadline,
EstimatedHours,
BudgetHours,
BudgetCost,
Description,
],
(err, result) => {
if (!err) {
success = true;
}
}
);
}
if (success == true){
return 0;
} else {
console.log("Name is already taken");
return "Name is already taken";
}
});
Your issue is to do with the asynchronous operation of NodeJS. What is happening in your code is all of your functions are getting "fired" because the code will execute sequentially, but it does not wait for the functions to return.
This is why your if / else statement is getting executed as the "else" - the query hasn't returned to set the "success" to true and therefore it hits the else statement because of its default value.
So we need to refactor your code a bit to make it suitable for async-await. The async-await will wait for a function to return before carrying on to the next statement. With the async-await command, the function it is calling must return a promise.
First we abstract your query into a function with a promise. A promise will run your query and only return when your query is complete:
async function Query (request) {
return new Promise ((resolve, reject) => {
const SEARCH_NAME = `SELECT ID FROM projects WHERE Name = '${request.Name}'`;
db.query(SEARCH_NAME, (err, results) => {
if (err) {
reject(err);
} else {
resolve(results)
}
});
})
}
The above function takes request, which would be your req.body as a parameter, carries out the query and returns the results.
We also need to refactor your insertDatabase function into async-await to see if our insert was successful or not (as this seems to be what you want to know):
async function insertDatabase(request) {
return new Promise((resolve, reject) => {
console.log("starting insert function");
const CREATE_NEW_PROJECT = "INSERT INTO projects (Name, Status, Deadline, EstimatedHours,
BudgetHours, BudgetCost, Description) VALUES (?,?,?,?,?,?,?)";
db.query(
CREATE_NEW_PROJECT,
[
request.Name,
request.Status,
request.Deadline,
request.EstimatedHours,
request.BudgetHours,
request.BudgetCost,
request.Description,
],
(err) => {
if (err) {
reject(false)
}
else {
resolve(true)
}
});
});
}
We then check the results for length less than 1:
var result = await Query(req.body);
if (result <1) {
console.log("No Duplicate, insert it")
success = await insertDatabase(req.body);
}
Putting it all together our code will look something like:
app.post("/api/createProject", async (req, res) => {
var success = false;
var result = await Query(req.body);
if (result <1) {
console.log("No Duplicate, insert it")
success = await insertDatabase(req.body);
if (success == true){
return 0;
} else {
console.log("Name is already taken");
return "Name is already taken";
}
}
}
async function Query (request) {
return new Promise ((resolve, reject) => {
const SEARCH_NAME = `SELECT ID FROM projects WHERE Name = '${request.Name}'`;
db.query(SEARCH_NAME, (err, results) => {
if (err) {
reject(err);
} else {
resolve(results)
}
});
})
}
async function insertDatabase(request) {
return new Promise((resolve, reject) => {
console.log("starting insert function");
const CREATE_NEW_PROJECT = "INSERT INTO projects (Name, Status, Deadline, EstimatedHours,
BudgetHours, BudgetCost, Description) VALUES (?,?,?,?,?,?,?)";
db.query(
CREATE_NEW_PROJECT,
[
request.Name,
request.Status,
request.Deadline,
request.EstimatedHours,
request.BudgetHours,
request.BudgetCost,
request.Description,
],
(err) => {
if (err) {
reject(false)
}
else {
resolve(true)
}
});
});
}
Notice our post callback has "async" in front of it. This is a required keyword in any function that contains the await command.
Additional:
I've left your code mostly how it was with a bit of refactoring, some extra code best practice notes for you:
Multiple return paths is a very bad idea as this leads to testing issues as well as later debugging nightmares, so I would suggest you refactor the return from the final if statement - maybe assign the variables in the if statement but return the variable at the end.
You shouldn't return different types from the same function. So either return 1 or 0 (true or false) on your result, or return two different sets of text. Do not return 0 for one case and text for another, this is very bad practice.
I've also short-cutted by passing your req.body into the functions which probably passes more data than needed. If you don't want to pass this, as a minimum you should encapsulate your assignments that go together into a JSON object:
var request = {
"Name" : req.body.Name,
"Status" : req.body.Status,
"Deadline" : req.body.Deadline,
"EstimatedHours" : req.body.EstimatedHours,
"BudgetHours" : req.body.BudgetHours,
"BudgetCost" : req.body.BudgetCost,
"Description" : req.body.Description
}
and then pass this variable request into the functions instead of req.body.
i'm also relatively new to this, but have an idea.
Try this:
make your function async
async function insertDatabase()
then await your query
await db.query()

how do I return values from an async each function?

I know this is a common problem for people, and I've looked up some guides and looked at the docs, but I'm not understanding what is happening, and would love some help.
Here is the controller function I am working on:
exports.appDetail = function (req, res) {
appRepo.findWithId(req.params.id, (err, myApp) => {
if (err) {
console.log(err)
}
getDeviceData(myApp.devices, (err, myDeviceData) => {
if (err) console.log(err)
console.log(JSON.stringify(myDeviceData) + ' || myDeviceData')
// construct object to be returned
let appsDataObject = {
name: myApp.name,
user_id: myApp.user_id,
devices: myDeviceData,
permissions: myApp.permissions
}
return res.status(200).send(appsDataObject)
})
})
}
// write async function here
const getDeviceData = function (devices, callback) {
let devicesDataArray = []
async.each(devices, function (device, cb) {
deviceRepo.findById(new ObjectID(device), (err, myDevice) => {
if (err) {
cb(err)
}
// get device data, push to devices array
let deviceObj = {
name: myDevice.name,
version: myDevice.version
}
devicesDataArray.push(deviceObj)
console.log(JSON.stringify(devicesDataArray) + ' || devicesDataAray after obj push')
})
cb(null, devicesDataArray)
}, function (err) {
// if any of the file processing produced an error, err would equal that error
if (err) console.log(err)
})
callback(null, devicesDataArray)
}
I originally wrote this with a for loop and a callback, but I think it was impossible to do that way (I'm not sure about that though). If there is a better way to make an asynchronous loop, please let me know.
On ~line 8 there is a log statement myDeviceData. This should be returning the data I want through a callback, but this log statement always comes back empty. And since the other log statements show that the data is being formatted correctly, the problem must be with returning the data I need through the callback of getDeviceData(). Presumably, the callback(null, devicesDataArray) should do this.
I'm not understanding how these async functions are supposed to work, clearly. Can someone please help me understand how I should get values from these async.each functions? Thank you.
EDIT:
I refactored the code to try and make it clearer and approach the problem better, and I have pinpointed where the problem is. At the beginning the this function I define devicesDataArray as an empty array, and at the end I return the array. Everything inside happens as it should, but I don't know how to tell the return to wait until the array isn't empty, if that makes sense, Here is the new code:
let getData = async function (devices) {
let devicesDataArray = []
for (let i = 0; i < devices.length; i++) {
deviceRepo.findById(new ObjectID(devices[i]), async (err, myDevice) => {
if (err) {
console.log(err)
}
console.log(JSON.stringify(myDevice) + ' || myDevice')
let deviceObj = await {
name: myDevice.name,
version: myDevice.version
}
console.log(JSON.stringify(deviceObj) + ' || deviceObj')
await devicesDataArray.push(deviceObj)
console.log(JSON.stringify(devicesDataArray) + ' || devicesDataArray after push')
})
}
console.log(JSON.stringify(devicesDataArray) + ' || devicesDataArray before return')
return Promise.all(devicesDataArray) // problem is here.
}
Any help towards understanding this is appreciated.
Here we are using a Promise
The Promise object is returned immediately and we perform our async code inside there.
const someFuncThatTakesTime = () => {
// Here is our ASYNC
return new Promise((resolve, reject) => {
// In here we are synchronous again.
const myArray = [];
for(x = 0; x < 10; x++) {
myArray.push(x);
}
// Here is the callback
setTimeout(() => resolve(myArray), 3000);
});
};
someFuncThatTakesTime().then(array => console.log(array));
first, I would prefer to avoid using callback because it will make your code unreadable,
also you can handle async using javascript methods without any needs to use other modules.
the reason for your problem is your async.each function executed without waiting what it has inside
and you have here deviceRepo.findById which is async function and need to wait for it
I refactored your code using async await and I wish this will simplify the problem
exports.appDetail = function async(req, res) {
try {
const myApp = await appRepo.findWithId(req.params.id)
const myDeviceData = await getDeviceData(myApp.device)
console.log(JSON.stringify(myDeviceData) + ' || myDeviceData')
// construct object to be returned
let appsDataObject = {
name: myApp.name,
user_id: myApp.user_id,
devices: myDeviceData,
permissions: myApp.permissions
}
return res.status(200).send(appsDataObject)
} catch (err) {
console.log(err)
}
}
// write async function here
const getDeviceData = function async(devices) {
let devicesDataArrayPromises = devices.map(async(device)=>{
const myDevice= await deviceRepo.findById(new ObjectID(device))
let deviceObj = {
name: myDevice.name,
version: myDevice.version
}
return deviceObj
})
const devicesDataArray = await Promise.all(devicesDataArrayPromises)
console.log(JSON.stringify(devicesDataArray) + ' || devicesDataAray after obj push')
return devicesDataArray
}

Data initializing with promises

I really struggle with data initializing and promises. I am using Ionic 3 with Angular and Ionic storage, but my question is mainly related to how promises operate.
Basically I would like to achieve the following:
when my app starts it should use the local storage collection
if the local storage collection does not exist or is empty, create a new one with http
if the http fails, create a collection with local data.
My solution so far:
getNewsItems():Promise<any> {
return this.storage.get(this.newsKey).then((data) => {
if(data == null)
{
return (this.buildNewsItemsViaHttp());
} else {
return (data);
}
});
}
private buildNewsItemsViaHttp(){
return new Promise(resolve => {
this.http.get('some/url/to/fetch/data')
.subscribe(
data => {
this.newsCollection = data;
this.storage.set(this.newsKey, this.newsCollection);
resolve(this.newsCollection);
},
(err) => {
resolve (this.buildNewsItemsViaLocalJSON());
}
);
});
}
private buildNewsItemsViaLocalJSON() {
return new Promise(resolve => {
this.http.get('assets/data/newsCollectionLocal.json')
.subscribe(
data => {
this.newsCollection = data;
this.storage.set(this.newsKey, this.newsCollection);
resolve(this.newsCollection);
},
(err) => {
console.log(err);
}
);
});}
I don't like some parts of it, for example returning a promise inside a promise - is this actually an issue?
Thanks in advance
A clean solution could be using async\await methods:
async buildNewsItemsViaHttp(){
return await this.http.get()....
}
async buildNewsItemsViaLocalJSON(){
return await this.http.get()....
}
async getNewsItems(){
return await this.storage.get()...
}
async getItems(){
return ((await this.getNewsItems()) || (await this.buildNewsItemsViaLocalJSON()) || (await this.buildNewsItemsViaHttp()));
}
usage:
const items = await this.getItems();
You can optimize the resources, cache them and return them in each function.
Example:
async buildNewsItemsViaHttp(){
let result = cache.get(); // todo
if(!result){
result = await this.http.get()...
cache.set(result)
}
return result;
}

Categories

Resources