Respond from method in controller without req, res from Express - javascript

I have a method that performs an update in Mongo but I do not have access to "res" and "res" since it is a method that I will use on more than one occasion.
My problem is when responding to the update as I try with a return but it doesn't work as the request never completes. Do you know how I can answer then?
This method calls the method that will be reused several times:
let item = (req, res = response ) => {
const { id } = req.params;
const { status, user, ...data } = req.body;
data.user = req.uid;
data.expenditure = null;
let item = {
concept: data.concept,
revenue: data.revenue,
'createdBy': {
uid: req.uid,
username: req.user.username,
},
description: data.description
}
createNewItem( id, item );
}
And this is the method to reuse several times that does not have "req" or "res":
const createNewItem = async ( id, item ) => {
try {
let pettycash = await PettyCash.findByIdAndUpdate( id,
{
$push: {
'items': {
$each: [item],
}
}
},{ new: true }
);
return { err: null, status: 200, pettycash };
} catch (err) {
return { err: err.toString(), status: 500, data: null };
}
}
Although the process is correct return { err: null, status: 200, pettycash }; doesn't finish the request so POSTMAN never finishes and you keep waiting for some response.
Thanks.

Okay so two options.
One pass res to function as argument which you don't want and is not a good idea because it violates single responsibility rule for a function.
You add await so that controller waits for the function execution to complete. To do this make your controller async. This is generally a good idea. You let controller do its work meanwhile your createNewItem only have single responsibility of creating an item.
let item = async (req, res = response ) => {
const { id } = req.params;
const { status, user, ...data } = req.body;
data.user = req.uid;
data.expenditure = null;
let item = {
concept: data.concept,
revenue: data.revenue,
'createdBy': {
uid: req.uid,
username: req.user.username,
},
description: data.description
}
await createNewItem( id, item );
// send response back
}

Related

DynamoDB only saving one record from JSON list

I'm trying to loop through a list of URLs in a JSON file, call an API and then save the results in the database. It loops through the whole list but only saves the first URL results. How can I get all URLs in the loop to be saved after each API call?
My code:
exports.putItemHandler = async (event) => {
const { body, httpMethod, path } = event;
if (httpMethod !== 'POST') {
throw new Error(`postMethod only accepts POST method, you tried: ${httpMethod} method.`);
}
// All log statements are written to CloudWatch by default. For more information, see
// https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-logging.html
console.log('received:', JSON.stringify(event));
console.log('----STARTING LOOP----')
console.log(v4())
console.log(urls)
var _id;
try {
for (const url of urls) {
_id = v4();
const resp = await axios.get(endpoint, {
params: {
key: API_KEY,
url: url
}
});
console.log(JSON.stringify(resp.data))
const lighthouseResults = resp.data;
const params = {
TableName: tableName,
Item: { id: _id,
created_at: new Date().toISOString(),
URL: url,
fullJson: lighthouseResults,
},
};
await docClient.put(params).promise();
const response = {
statusCode: 200,
body,
};
console.log(`response from: ${path} statusCode: ${response.statusCode} body: ${response.body}`);
return response;
}
} catch (err) {
console.error(err)
}
};

Promise resolves before loop completes in sails

I have a list of posts containing userId. When fetching n no.of post, I want to loop throught and get and fetch user data and append them into the post.
But before the loop gets resolved, the function gets return with undefined. After that, the post data gets listed but I want the post data to be fetched first.
I am new to promises and async. If there are any other solutions that I can use, then please notify me.
I am using sailsjs.
fetchPosts: async (req, res) => {
let feeds = [];
posts = await Posts.find({
skip: offset,
limit: limit,
sort: sort,
});
if (posts) {
/**
* LOOPING THROUGH FETCHED POST LOOP TO GET
* USER DATA, LIKE, SHARE, FOLLOW, BOOKMARKS
*/
const functionWithPromise = (post) => {
//a function that returns a promise
console.log(feeds);
return Promise.resolve(post);
};
const anotherAsyncFunction = async (post) => {
return functionWithPromise(post);
};
const getUser = async (userId, post) => {
return new Promise(async (resolve, reject) => {
const user = await Account.findOne({ id: userId });
if (user) {
post = {
...post,
user: {
id: user.id,
uName: user.uName,
provider: user.provider,
dpURL: user.dpURL,
provider: user.provider,
},
};
resolve(post);
} else {
reject(null);
}
});
};
const anAsyncFunction = async (post) => {
if (post.isAdminPost) {
post = {
...post,
user: {
id: "5f3b8bf00dc3f12414b7f773", // this is usedid of admin#dopaminetalks.com in `Admin` model
uName: "DTOfficial",
provider: "LOCAL",
dpURL: "/dpURL/86a73b80-babc-4caa-a84c-762f6e9c1b36.png",
},
};
feeds = [...feeds, post];
return anotherAsyncFunction(feeds);
} else {
getUser(post.userId, post).then((post) => {
feeds = [...feeds, post];
return anotherAsyncFunction(feeds);
});
}
};
const getData = async () => {
return Promise.all(posts.map((post) => anAsyncFunction(post)));
};
getData().then((data) => {
console.log(data);
return res.json({
status: true,
msg: "Posts Fetched",
data: data,
});
});
}
},

Firebase function to get ranking

I am new to firebase and would like to implement the ranking for the players stored in the rank table from firebase real-time db, see screenshot below:
I have also setup the rules for the index:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"TH": {
"rank": {
".indexOn" : ["balance", "w_rate","match"]
}
}
}
}
in my firebase function, I have a function as a post request to get the rank:
exports.getRank = functions.https.onRequest(async (req,res) => {
const user_id = req.query.id;
console.log(`user_id ${user_id} `);
const query = database().ref('TH/rank')
.orderByChild('balance')
.limitToLast(30)
query.once('value',function(snapshot) {
console.log('in side the usersRef');
console.log(`snapshot:${JSON.stringify(snapshot.val())}`);
let current_user_rank = 0;
if (user_id !== null) {
console.log(`user id ${user_id} isn't null`);
database().ref('TH/rank/').orderByChild('balance').once('value',function(all_snapshot){
let index = 0;
console.log(`user id ${user_id} all snapshot.`);
if (all_snapshot.child(`${user_id}`).exists()) {
console.log(`user id ${user_id} exists`);
const current_user_data = all_snapshot.child(`${user_id}`).val();
all_snapshot.forEach( child => {
index += 1;
console.log(child.key, child.val());
if(child.key === user_id) {
current_user_rank = all_snapshot.numChildren() - index;
}
});
res.json({
user: { id: user_id,
rank: current_user_rank,
data: current_user_data
},
rank: snapshot.val()
});
} else {
res.json({
user: { id: user_id,
rank: current_user_rank,
data: null
},
rank: snapshot.val()
});
}
}).catch();
} else {
res.json({
user: { id: user_id,
rank: current_user_rank,
data: null
},
rank: snapshot.val()
});
}
}).catch();
});
However, the result isn't correct, it seems the orderByChild doesn't work at all. Can someone help on this?
Thanks.
There are several problems with your Cloud Function:
You use async when declaring the callback but you never use await in the Cloud Function, in order to get the result of the asynchronous once() method.
Instead of using the callback "flavor" of the once() method (i.e. ref.once('value',function(snapshot) {..}), use the promise "flavor" (i.e., with await: await ref.once('value');).
The result is that you don't correctly manage the Cloud Function life cycle. For more details on how to do that correctly, I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series as well as read the following doc.
So the following should do the trick. (Note that I've just adapted it to correctly manage the life cycle, I've not tested the business logic).
exports.getRank = functions.https.onRequest(async (req, res) => {
try {
const user_id = req.query.id;
console.log(`user_id ${user_id} `);
let current_user_rank = 0;
if (user_id !== null) {
console.log(`user id ${user_id} isn't null`);
const baseQuery = database().ref('TH/rank/').orderByChild('balance');
const query = baseQuery.limitToLast(30);
const snapshot = await query.once('value');
console.log(`snapshot:${JSON.stringify(snapshot.val())}`);
const all_snapshot = await baseQuery.once('value');
let index = 0;
console.log(`user id ${user_id} all snapshot.`);
if (all_snapshot.child(`${user_id}`).exists()) {
console.log(`user id ${user_id} exists`);
const current_user_data = all_snapshot.child(`${user_id}`).val();
all_snapshot.forEach(child => {
index += 1;
console.log(child.key, child.val());
if (child.key === user_id) {
current_user_rank = all_snapshot.numChildren() - index;
}
});
res.json({
user: {
id: user_id,
rank: current_user_rank,
data: current_user_data
},
rank: snapshot.val()
});
} else {
res.json({
user: {
id: user_id,
rank: current_user_rank,
data: null
},
rank: snapshot.val()
});
}
} else {
res.json({
user: {
id: user_id,
rank: current_user_rank,
data: null
},
rank: snapshot.val()
});
}
} catch (error) {
console.log(error);
res.status(500).send(error);
}
});
Update following your comment:
You need to use forEach() in order to get the children correctly ordered, and not snapshot.val(). snapshot.val() displays the children according to their key, exactly like they are ordered in the DB. The following adaptation of the code in your comment works correctly:
exports.getSortedRank = functions.https.onRequest(async (req, res) => {
try {
const obj = {};
const baseQuery = admin.database().ref('TH/rank/').orderByChild('balance');
const query = baseQuery.limitToLast(10);
const snapshot = await query.once('value');
snapshot.forEach(childSnapshot => {
console.log(childSnapshot.val());
obj[childSnapshot.key] = childSnapshot.val()
});
res.json({ rank: obj });
} catch (error) { console.log(error); res.status(500).send(error); }
});

Async/Await : called function is not awiting and returning value

Hello I am creating 1 function with dynamic arguments where as I am calling api and on defined route I am calling express middleware function and from there I am calling another dynamic function which will help me to insert data into the database.
I am using Sequalize ORM
Here is code:
var async = require('async');
// Models
var LogSchema = require('../models/Logs')
module.exports = {
insertLog: async (req, res) => {
let result = await insertLogFn('1', 'method_name()', 'module_name_here', 'req.body', '{ api response }', 'action', '24')
console.log("result", result)
res.status(200).json(result)
}
};
function insertLogFn(status, invokedMethodName, moduleName, bodyRequest, apiResponse = null, actionName = null, userId) {
async.waterfall([
(nextCall) => {
let dataToBeInserted = {}
dataToBeInserted.status = status,
dataToBeInserted.invoked_method_name = invokedMethodName,
dataToBeInserted.module_name = moduleName,
dataToBeInserted.body_request = bodyRequest,
dataToBeInserted.api_response = apiResponse
dataToBeInserted.action_name = actionName,
dataToBeInserted.user_id = userId
LogSchema.create(dataToBeInserted).then(res => {
const dataObj = res.get({plain:true})
nextCall(null, {
status: 200,
message: "Log inserted successfully",
data: dataObj
})
}).catch(err => {
})
}
], (err, response) => {
if(err) {
}
return response
})
}
In module.export I have added insertLog function which is getting called in api and from there I am calling insertLogFn() which is declared outside of the module.export.
I am able to get inserted result in function insertLogFn() but the things is await is not working and not waiting for the result.
What I want to do is to wait till insertLogFn gets executed and the returned response has to be stored in the variable and return it as an api response.
You cannot. As per my understanding, IMO, Thumb rule is "Async/Await operation should return a promise"
function insertLogFn(status, invokedMethodName, moduleName, bodyRequest, apiResponse = null, actionName = null, userId) {
async.waterfall([
(nextCall) => {
let dataToBeInserted = {}
dataToBeInserted.status = status,
dataToBeInserted.invoked_method_name = invokedMethodName,
dataToBeInserted.module_name = moduleName,
dataToBeInserted.body_request = bodyRequest,
dataToBeInserted.api_response = apiResponse
dataToBeInserted.action_name = actionName,
dataToBeInserted.user_id = userId
LogSchema.create(dataToBeInserted).then(res => {
const dataObj = res.get({plain:true})
nextCall(null, {
status: 200,
message: "Log inserted successfully",
data: dataObj
})
return ;
console.log("you should return something here<-------");
}).catch(err => {
})
}
], (err, response) => {
if(err) {
}
return response
})
}
Now the answer will be clear if you read this one from Bergi: https://stackoverflow.com/a/40499150/9122159

Callback NodeJS & insert data with mongoose

I know this topic as already asked many times before but I didn't find the right answer to do what I want.
Actually, I try to save two different list of JSON object in MongoDB via Mongoose. To perform both at the same time I use 'async'.
However, when I save it with the command insertMany() I get an error because he calls the callback of async before finishing the insertMany(). Therefore answer[0] is not defined.
What will be the proper way of doing it ?
Here is my code with the async:
const mongoose = require("mongoose");
const async = require("async");
const utils = require("../utils");
const experimentCreate = function(req, res) {
let resData = {};
let experimentList = req.body.experiment;
let datasetList = req.body.datasetList;
async.parallel(
{
dataset: function(callback) {
setTimeout(function() {
answer = utils.createDataset(datasetList);
callback(answer[0], answer[1]);
}, 100);
},
experiment: function(callback) {
setTimeout(function() {
answer = utils.createExp(experimentList);
callback(answer[0], answer[1]);
}, 100);
}
},
function(err, result) {
if (err) {
console.log("Error dataset or metadata creation: " + err);
sendJSONresponse(res, 404, err);
} else {
console.log("Experiment created.");
resData.push(result.dataset);
resData.push(result.experiment);
console.log(resData);
sendJSONresponse(res, 200, resData);
}
}
);
};
Then the two functions called createExp and createDataset are the same in another file. Like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
return [err, null];
} else {
console.log("All dataset created.");
return [null, ds];
}
});
};
There's a few problems with your code. For one, you're not returning anything in your createDataset function. You're returning a value in the callback of insertMany but it doesn't return that value to the caller of createDataset as it's within another scope. To solve this issue, you can wrap your Dataset.insertMany in a promise, and resolve or reject depending on the result of Data.insertMany like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
return new Promise((resolve, reject) => {
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
reject(err);
} else {
console.log("All dataset created.");
resolve(ds);
}
});
});
};
Now your return object is no longer going to be an array so you won't be able to access both the error and the result via answer[0] and answer[1]. You're going to need to chain a then call after you call createDataset and use callback(null, answer) in the then call (as that means createDataset executed successfully) or use callback(err) if createDataset throws an error like below:
dataset: function(callback) {
setTimeout(function() {
utils.createDataset(datasetList).then(answer => {
callback(null, answer);
}).catch(err => callback(err)); // handle error here);
}, 100);
}
Note: You'll most likely need to alter your createExp code to be structurally similar to what I've produced above if it's also utilizing asynchronous functions.

Categories