When i am using async and await in nodejs its response is coming slow PRODUCT SCHEMA
const mongoose = require('mongoose');
const productSchema = mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, require: true},
category_fields: { type:Object },
name_list_fields: [{ type:mongoose.Schema.Types.ObjectId, ref:'list' }],
full_name: { type:String },
actual_price: { type: Number, default: 0 },
selling_price: { type: Number, default: 0 },
product_image_url_1: { type: String, default: "" },
sku: { type: String, default: "" },
brand:{type:mongoose.Schema.Types.ObjectId, ref:'brand' },
linked_offers: [{ type:mongoose.Schema.Types.ObjectId, ref:'offer' }],
hot_deal: { type:Boolean,default:false}, });
CODE
return new Promise(function(resolve, reject){
productschema.find({delete_status: { $ne: 1}})
.sort({_id:-1})
.populate('brand similar_product linked_offers','name actual_price product_image_url_1 actual_price selling_price full_name offer_image_url offer_type offer_amount_percentage description')
.skip( parseInt(req.params.index) )
.limit( parseInt(req.params.limit) )
.then(async productsrows => {
if(productsrows.length == 0){
resolve({
"success":false,
"message":"No Data"
})
}else{
try{
var arrayListOfProduct = [];
for(var i =0;i<productsrows.length;i++){
var item = productsrows[i].toObject()
item.in_stock = await commonFunctionAdmin.fetchProductCountInStock(productsrows[i]._id)
arrayListOfProduct.push(item)
}
resolve({
"success":true,
"message":"Products fetched success",
"count":await fetchTotalProductCount({delete_status: { $ne: 1}}),
"data":arrayListOfProduct
});
}catch(e){
console.log(e)
resolve({
"success":false,
"message":"something went wrong"
})
}
}
})
}) //STOCK COUNT
FUNCTION
return new Promise(function(resolve, reject){
stockSchema.aggregate([
{$unwind: "$product"},
{$unwind: "$product.imei"},
{$match: {"product.imei.sold_status":false,"product.product":ObjectId(product_id)}},
{$group: {
_id: null,
count: { $sum: 1 }
}
}]).then(async rowsTotalRevenue => {
if(rowsTotalRevenue.length > 0){
resolve(rowsTotalRevenue[0].count)
}else{
resolve(0)
}
}).catch(e=>{
console.log(e)
resolve(0)
})
});
Usually when you use await keyword, you save something like 200ms from each request (my experience).
To understand what is happening in your application, you can put a timer in every essential step of your function, measure the time difference from start to finish. This is very simple to do, just check what hours the code started to be run.
Async/await makes asynchronous code look and behave a little more like synchronous code. This is where all its power lies.
I tried to understand the code posted above and saw that you have some iterations in the array, know that all of this might be a bottleneck for your answer.
async function myCoolFunction () {
var initial = new Date().getTime();
var finalTime;
// create a new promise inside of the async function
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(true), 1000) // resolve
});
// wait for the promise to resolve
let result = await promise;
finalTime = newDate().getTime();
}
myCoolFunction();
}
Related
I came to a problem, where I can create conversations with multiple people 2 and so on. However, I can't understand why it doesn't store data to seperate User models.
Here is a code that you only need to know:
router.post(
"/",
auth,
[
check("conversators", "There should be at least two conversators").isLength(
{ min: 2 }
),
],
async (req, res) => {
const { conversators } = req.body;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
let conversation = new Conversation({
user: req.user.id,
conversators: conversators,
});
await conversators.map(async (conversator) => {
let user = await User.findById(conversator);
let newData = user;
newData.conversations.push(conversation.id);
console.log('Created data', newData);
let newUser = await User.findOneAndUpdate(
{ user: conversator },
{
$set: {
newData,
},
},
{ new: true }
);
await newUser.save();
console.log(newUser);
});
await conversation.save();
res.status(200).json(conversation);
} catch (error) {
console.error(error.message);
res.status(500).send("Server error.");
}
}
);
module.exports = router;
What I can assure is that this line: console.log('Created data', newData); prints the desired data. However, the next console: console.log(newUser); prints the same User model as the previous one.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
name: {
type: String,
required: true,
},
surname: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
conversations: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "conversation",
},
],
date: {
type: Date,
default: Date.now,
},
});
module.exports = User = mongoose.model("user", UserSchema);
The reason might be the difference in search methods used to get a record for newData and newUser. You have used User.findById for newData, which will obviously return different objects for different ids. But User.findOneAndUpdate uses filter criteria that may satisfy several results, but only first will be returned. So it boldly depends on what that user field is.
Here is the part that I changed and started to see the data on MongoDB:
await conversators.map(async (conversator) => {
let user = await User.findById(conversator);
let newData = user;
newData.conversations.push(conversation.id);
new Promise(async (resolve, reject) => {
user = await User.findOneAndUpdate(
{ id: conversator },
{
$set: {
newData,
},
},
{ new: true }
);
return resolve;
})
return await user.save();
});
Posted on behalf of the question asker
I wrote this function to update a data in MongoDB:
const Sequence = require('../models/sequenceSchema');
module.exports.genNumber = function () {
let doc = Sequence.findByIdAndUpdate(
{_id: "5e1f72951c9d440000afce53"},
{$inc:{sequence_value: 1}},
{new: true},
function(err,result){
if(err) return console.log(err)
return result.sequence_value;
}
)
};
In the Mutation resolve I wrote this code:
const graphql = require('graphql');
const functions = require('../modules/functions');
const Mutation = new GraphqlObjectType({
(..)
resolve(parent,args){
let ticket = new Ticket({
name: args.name,
number: functions.genNumber(),
opendate: Date(),
product: args.product,
priority: args.priority,
status: args.status,
deadline: calcs.deadline(Date(),args.priority),
userid: args.userid
});
My issue is that the function findByIdAndUpdate() update the value on MongoDB, but is not assigning to the property number:. If I return the result in a console log it shows perfectly, I'm not understanding what is happening. I'm using Graphql/Mongoose/MongoDB
It's happening because:
The genNumber function is not returning anything
If the genNumber function returns something, it would be a promise and the promise needs to be resolved before being used when creating a Ticket.
This updated code snippet should work:
const Sequence = require('../models/sequenceSchema');
module.exports.genNumber = function () {
// Notice the return statement below
return new Promise((resolve, reject) => {
Sequence.findByIdAndUpdate(
{ _id: "5e1f72951c9d440000afce53" },
{ $inc: { sequence_value: 1 } },
{ new: true },
function (err, result) {
if (err) {
console.log(err);
return reject(err);
}
console.log('Result: ', result);
return resolve(result.sequence_value);
}
)
})
};
=
const Mutation = new GraphqlObjectType({
// (..)
// Notice the resolve method becomes an asynchronous
resolve: async (parent, args) => {
// Notice the await on functions.genNumber call
const number = await functions.genNumber();
let ticket = new Ticket({
name: args.name,
number: number,
opendate: Date(),
product: args.product,
priority: args.priority,
status: args.status,
deadline: calcs.deadline(Date(), args.priority),
userid: args.userid
});
}
})
I used this method because I am storing an array of classified messages, I would like to vividly understand why it doesn't update.
Here's the db.js:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ObjectId = mongoose.Types.ObjectId;
const usersessionSchema = new Schema({
fb_id: String,
fb_name: String,
fb_profpic: String,
message_body: [
{
message: String,
message_type: String,
timestamp: String
}
],
admin: Boolean,
branch: String
});
const model = (prefix) => {
prefix = prefix || '';
console.log(prefix);
if (prefix.length > 0) {
return mongoose.model(prefix + "-usersessions", usersessionSchema);
} else {
return new Error('Undefined collection prefix!');
}
}
/** Push message into message body*/
module.exports.pushsession =
async(model, id, data) => {
return new Promise((resolve, reject) => {
console.log(data);
model.findOneAndUpdate({fb_id: id}, {$push: {data}},{safe: true})
.then(res => {
console.log(res);
/
resolve(res);
})
.catch(err => {
reject(err);
console.log(err);
throw err;
});
});
}
Here's the controller.js:
/** Push usersession message */
module.exports.pushsession =
async(req, res, next) => {
try {
//jwt.validateToken(req);
var en = "en";
var dateEn = moment().locale(en);
format = "MM/DD/YYYY h:mm:ss A"; //h:mm:ss.SSS if you want miliseconds
var datetime_now = dateEn.format(format);
console.log(datetime_now);
var request = {
message_body: {
message: req.body.message,
message_type: req.body.message_type,
timestamp: datetime_now
}
};
const model = usersessionDB(req.query['client']);
const id = req.body.fb_id;
const result = await usersessionDB.pushsession(model, id, request);
if (result) {
response.success(res, next, result, 200, response.HTTP_STATUS_CODES.ok);
} else {
response.failure(res, next, {
message: 'ID does not exist'
}, 404, response.HTTP_STATUS_CODES.not_found);
}
} catch (err) {
response.failure(res, next, err, 500, response.HTTP_STATUS_CODES.internal_server_error);
}
}
Here's the route.js:
const controller = require('../controller/usersession-controller');
module.exports =
(server) => {
server.post('/api/session', controller.create);
server.get('/api/session', controller.list);
server.get('/api/session/:id', controller.get);
server.put('/api/session/:id', controller.update);
server.del('/api/session/:id', controller.delete);
server.put('/api/pushsession', controller.pushsession);
}
Visually, if you run this using postman, you can see that it display the one I want to search and update
Result of the postman
What I want to happen is to insert another set of array inside that message_body
Data I've inserting
My desired output
This code is working without that promise something, but in my project it is needed so I can't remove that thing.
So, based on :
This code is working without that promise something
i can point a thing or two,
in db.js
module.exports.pushsession =
async(model, id, data) => {
return new Promise((resolve, reject) => {
you don't need async since you're returning a promise so replace this
async(model, id, data) => {
with
(model, id, data) => {
and since you're returning a promise and removed async , you don't need the await on the other side ( controller.js ), so this
const result = await usersessionDB.pushsession(model, id, request);
if (result) {
response.success(res, next, result, 200, response.HTTP_STATUS_CODES.ok);
} else {
should be
usersessionDB.pushsession(model, id, request).then(
(result) => { // when resolved
response.success(res, next, result, 200, response.HTTP_STATUS_CODES.ok);
},
(err) => { // when rejected
response.failure(res, next, {
message: 'ID does not exist'
}, 404, response.HTTP_STATUS_CODES.not_found);
});
this is a comparison between async/await and promises : Javascript Promises vs Async Await. Difference?
and here's some good examples of using promises : https://medium.com/dev-bits/writing-neat-asynchronous-node-js-code-with-promises-32ed3a4fd098
i think your $push is ok but you already said
This code is working without that promise something
i hope this helps and Good luck :)
I tried cleaning my code
here's the controller.js:
/** Push usersession message */
module.exports.pushsession =
async (req, res, next) => {
try {
//jwt.validateToken(req);
var en = "en";
var dateEn = moment().locale(en);
format = "MM/DD/YYYY h:mm:ss A"; //h:mm:ss.SSS if you want miliseconds
var datetime_now = dateEn.format(format);
console.log(datetime_now);
var data = {
message: req.body.message,
message_type: req.body.message_type,
timestamp: datetime_now
};
const model = usersessionDB(req.query['client']);
const id = req.body.fb_id;
console.log(id);
const result = await usersessionDB.pushsession(model, id, data).then(
(result) => { // when resolved
response.success(res, next, result, 200, response.HTTP_STATUS_CODES.ok);
},
(err) => { // when rejected
response.failure(res, next, {
message: 'ID does not exist'
}, 404, response.HTTP_STATUS_CODES.not_found);
});
} catch (err) {
response.failure(res, next, err, 500, response.HTTP_STATUS_CODES.internal_server_error);
}
}
Here's the db.js:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ObjectId = mongoose.Types.ObjectId;
const usersessionSchema = new Schema({
fb_id: String,
fb_name: String,
fb_profpic: String,
message_body:[{
message: String,
message_type: String,
timestamp: String
}],
admin: Boolean,
branch: String
});
/** Push message into message body*/
module.exports.pushsession =
async(model, id, data) => {
console.log(data);
return new Promise((resolve, reject) => {
model.findOneAndUpdate({fb_id: id}, { $push: { message_body: data }})
.then(res => {
console.log(res);
resolve(res);
})
.catch(err => {
reject(err);
console.log(err);
throw err;
});
});
}
Out of the blue after I tried to replace $push with $set then again I replace it with $push, it worked.
I don't if there's a difference, or I miss something, feel free to point it out.
I am trying to make a rest call and update a list and then resolve the promise with the updated list.
function addTestCaseToTestRail(){
return new Promise(function(resolve){
compareTestRailAndProtractor().then(function(tests){
var testsLength = tests.tests.length;
var url = testRailURL+testRailData.addTestEndPoint;
for(var i=0; i<testsLength; i++){
if(tests.tests[i].id==undefined){
var newId=""
var options = {
url:url,
headers:headers,
body:{
"title":tests.tests[i].name,
"custom_jira_component" : 465
},
json: true
}
request.post(options, function(err, httpResponse, body){
if (err) {
console.error(err);
return;
}
newId = body.id;
});
tests.tests[i].id = newId;
}
}
resolve(tests);
});
});
}
function test(){
addTestCaseToTestRail().then(function(tests){
console.log(tests);
});
}
test()
The request is getting posted and I am able to create tests in test rail but the resolve(tests) does not have the newId assignment.
This is the output I am getting. Not sure why resolve does not wait for the rest call to complete.
{ tests:
[ { id: '', name: 'test1'},
{ id: '', name: 'test2'},
{ id: '', name: 'test3'},
{ id: '', name: 'test4'},
{ id: '', name: 'test6'},
{ id: '', name: 'test5'} ] }
compareTestRailAndProtractor returns a Promise. You can use async/await within .then() and Promise constructor within for loop to await request callback, which is issue at code at Question, as the for loop does not await the callback function
function addTestCaseToTestRail() {
return compareTestRailAndProtractor()
.then(async function(tests) {
var testsLength = tests.tests.length;
var url = testRailURL + testRailData.addTestEndPoint;
for (var i = 0; i < testsLength; i++) {
await new Promise((resolve, reject) => {
if (tests.tests[i].id == undefined) {
var newId = ""
var options = {
url: url,
headers: headers,
body: {
"title": tests.tests[i].name,
"custom_jira_component": 465
},
json: true
}
request.post(options, function(err, httpResponse, body) {
if (err) {
reject(err);
}
newId = body.id;
tests.tests[i].id = newId;
resolve();
});
} else {
resolve()
}
});
}
return tests
})
}
function test() {
addTestCaseToTestRail()
.then(function(tests) {
console.log(tests);
})
.catch(function(err) {
console.error(err)
})
}
test()
I think it's because of the non-blocking nature of Javascript. The thing is that "resolve(test)" gets executed before your "post" event has a response (which is the one where you take the Id from).
I would recommend you to take a look at https://caolan.github.io/async/ Async is an amazing library for handling async processes.
Good luck!
EDIT: You can also take a look at async/await JS operators
I'm busy working on an endpoint for a reporting system. Node being async is giving me issues, although I'd rather not force it to be synchronous.
We're using MongoDB and Mongoose. I've got to query regex over collection A, then for each document that gets returned, query multiple contained documents to populate a JSON object/array to be returned.
I can use populate for most of the data, except the final looped queries which is where the async kicks in and returns my report early. Is there an elegant way to do this? Or should I be splitting into a different function and calling that multiple times to stick to the functions should do only one thing rule?
Example Code:
A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) {
var report = [];
A.map(function(a)){
report[a.name] = [];
D.aggregate([
{
$match: {
id: B._id
}
},
{
$group: {
_id: null,
count: { $sum: 1 }
}
}
], function(err, result) {
C.map(function(c){
report[a.name].push({
'field1': c.field1,
'field2': c.field2,
'field3': c.field3,
'count': result.count
});
});
});
}
return report;
});
The issue here is with the logic / async. Not with the syntax, hence the semi-pseudo code.
Any help or advice would be greatly appreciated.
You need to familiarize yourself with promises, and with async in general.
Because you are returning an array, that's the value you are going to get.
You have a few options when dealing with Async, but in your case, you want to look at two solutions:
// callbacks
getSetOfIDs((err, ids) => {
let remaining = ids.length;
let things = [];
let failed = false;
ids.forEach(id => {
getThingByID(id, (err, thing) => {
if (failed) { return; }
if (err) {
failed = true;
handleFailure(err);
} else {
remaining -= 1;
things.push(thing);
if (!remaining) {
handleSuccess(things);
}
}
});
});
});
Note, I'm not returning things, I'm passing it into a callback.
You can use higher-order functions to clean this sort of thing up.
// cleaned up callbacks
function handleNodeCallback (succeed, fail) {
return function (err, data) {
if (err) {
fail(err);
} else {
succeed(data);
}
};
}
function handleAggregateCallback (succeed, fail, count) {
let items = [];
let failed = false;
const ifNotFailed = cb => data => {
if (!failed) { cb(data); }
};
const handleSuccess = ifNotFailed((item) => {
items.push(item);
if (items.length === count) { succeed(items); }
});
const handleFailure = ifNotFailed((err) => {
failed = true;
fail(err);
});
return handleNodeCallback(handleSuccess, handleFailure);
}
A little helper code later, and we're ready to go:
// refactored callback app code (note that it's much less scary)
getSetOfIDs((err, ids) => {
const succeed = (things) => app.display(things);
const fail = err => app.apologize(err);
if (err) { return fail(err); }
let onThingResponse = handleAggregateCallback(succeed, fail, ids.length);
ids.forEach(id => getThingByID(id, onThingResponse));
});
Note that aside from higher-order functions, I'm never returning anything, I'm always passing continuations (things to do next, with a value).
The other method is Promises
// Promises
getSetOfIDs()
.then(ids => Promise.all(ids.map(getThingByID)))
.then(things => app.display(things))
.catch(err => app.apologize(err));
To really get what's going on here, learn Promises, the Promise.all static method, and array.map().
Both of these sets of code theoretically do the exact same thing, except that in this last case getSetOfIDs and getThingByID don't take callbacks, they return promises instead.
usually in async calls, after return statement any operations are cancelled.
maybe you can return report object only when all is done and well.
A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) {
var report = [];
A.map(function(a)){
report[a.name] = D.aggregate([
{
$match: {
id: B._id
}
},
{
$group: {
_id: null,
count: { $sum: 1 }
}
}
], function(err, result) {
if(err){
return [];
}
var fields = []
C.map(function(c){
fields.push({
'field1': c.field1,
'field2': c.field2,
'field3': c.field3,
'count': result.count
});
});
return fields;
});
}
return report;
});
Just use promises:
A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) {
var report = [];
return Promise.all([
A.map(function(a)){
return new Promise(function(resolve, reject) {
report[a.name] = [];
D.aggregate([{ $match: { id: B._id }},{$group: {_id: null,count: { $sum: 1 }}}],
function(err, result) {
if(err) {
reject(err)
} else {
C.map(function(c){
report[a.name].push({
'field1': c.field1,
'field2': c.field2,
'field3': c.field3,
'count': result.count
});
});
resolve(report)
}
});
}
})])
})
.then(function(report){
console.log(report)
})
.catch(function(err){
console.log(err)
})