Why my Array is a empty after .then()? JavaScript [duplicate] - javascript

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 4 years ago.
I wrote a controller and I do not understand why in the method then my array is correct and I need to send it, and for .then () my array is empty. I can not send a res in the middle of the loop.
exports.getRecipientdata = (req, res) => {
const userId = req.params.recipientId;
const sendersArray = [];
Transaction.findAll({
where: {
id_recipient: userId,
},
}).then(transactions => {
for (let i = 0; i < transactions.length; i++) {
User.findOne({
where: {
id: transactions[i].id_sender,
},
attributes: ['id', 'name', 'surname'],
include: [
{
model: Transaction,
where: { id_sender: db.Sequelize.col('user.id') },
attributes: [
'amount_money',
'date_time',
'transfer_title',
'id_recipient',
'id_sender',
],
},
],
})
.then(sender => {
sendersArray.push(sender);
console.log(JSON.stringify(sendersArray)); // ok
})
.catch(err => {
console.log(err);
});
}
console.log('sendersArray', sendersArray); // empty?
res.send(sendersArray);
});
};

The for loop is sending out requests which asynchronously populate sendersArray. If you console.log(sendersArray) synchronously after the for loop has run, it won't have been populated yet. Instead of a for loop, use .map and Promise.all to wait for all requests to complete:
exports.getRecipientdata = (req, res) => {
const userId = req.params.recipientId;
const sendersArray = [];
Transaction.findAll({
where: {
id_recipient: userId,
},
}).then(transactions => {
return Promise.all(transactions.map(({ id_sender }) => (
User.findOne({
where: {
id: id_sender,
},
attributes: ['id', 'name', 'surname'],
include: [
{
model: Transaction,
where: { id_sender: db.Sequelize.col('user.id') },
attributes: [
'amount_money',
'date_time',
'transfer_title',
'id_recipient',
'id_sender',
],
},
],
})
.then(sender => {
sendersArray.push(sender);
})
.catch(err => {
console.log(err);
})
)));
})
.then(() => {
res.send(sendersArray);
});
};
Another possibility, rather than pushing to an outer variable, would be to use the array created by Promise.all, and filter by boolean to remove the falsey values (since the catch's lack of a return value will have resulted in undefineds being present in the result of the Promise.all array):
exports.getRecipientdata = (req, res) => {
const userId = req.params.recipientId;
Transaction.findAll({
where: {
id_recipient: userId,
},
}).then(transactions => {
return Promise.all(transactions.map(({ id_sender }) => (
User.findOne({
where: {
id: id_sender,
},
attributes: ['id', 'name', 'surname'],
include: [
{
model: Transaction,
where: { id_sender: db.Sequelize.col('user.id') },
attributes: [
'amount_money',
'date_time',
'transfer_title',
'id_recipient',
'id_sender',
],
},
],
})
.catch(err => {
console.log(err);
})
)));
})
.then((sendersArray) => {
res.send(sendersArray.filter(Boolean));
});
};

Related

How to chain multiple updateMany() requests to MongoDB (and wait for each response to come back before moving on)

I am trying to make 3 different updateMany requests inside a get request, each with a different query. They all work except that my third updateMany request only achieves the desired behaviour after the page reloads/refreshes two times.
Here is my code:
app.get('/', (req, res) => {
let todaysDate = new Date().toString().split(' ').slice(0, 4).join(' ')
let todaysDateMs = new Date(todaysDate + ', 00:00:00').getTime()
habitsCollection.updateMany({}, {
$set: {
todaysDate,
todaysDateMs
}
}).then(res => {
habitsCollection.updateMany({ lastClicked: { $ne: todaysDate } }, {
$set: {
clicked: 'false'
}
}).then(res => {
habitsCollection.updateMany({ $expr: { $gte: [{ $subtract: ["$todaysDateMs", "$lastClickedMs"] }, 172800000] } }, {
$set: {
streak: 0
},
})
})
})
habitsCollection.find({}).toArray()
.then(results => {
console.log(results)
let filtered = results.filter(result => result.clicked === 'false')
habitsLeft = filtered.length
res.render('index.ejs', { habits: results, dayVar: 'days', habitsLeft })
})
})
I am expecting that on each page load, if a document has a lastClickedMs key/value subtract todaysDateMs key/value and is greater than or equal to 172800000, that the streak value will be reset to 0. This does happen but only after the page has loaded twice.
I think the problem is that you are not waiting for the promises to resolve before you call habitsCollection.find({}).toArray(). You can use Promise.all to handle multiple promises.
app.get('/', (req, res) => {
let todaysDate = new Date().toString().split(' ').slice(0, 4).join(' ');
let todaysDateMs = new Date(todaysDate + ', 00:00:00').getTime();
Promise.all([
habitsCollection.updateMany({}, {
$set: {
todaysDate,
todaysDateMs
}
}),
habitsCollection.updateMany({ lastClicked: { $ne: todaysDate } }, {
$set: {
clicked: 'false'
}
}),
habitsCollection.updateMany({ $expr: { $gte: [{ $subtract: ["$todaysDateMs", "$lastClickedMs"] }, 172800000] } }, {
$set: {
streak: 0
},
})
]).then(() => {
habitsCollection.find({}).toArray()
.then(results => {
console.log(results);
let filtered = results.filter(result => result.clicked === 'false');
habitsLeft = filtered.length;
res.render('index.ejs', { habits: results, dayVar: 'days', habitsLeft });
});
});
});

How to use DataLoader with Mongoose

I'm trying to build the following use case of DataLoader together with Mongoose:
export const PurchaseOrderType = new GraphQLObjectType({
name: "PurchaseOrder",
description: "PurchaseOrder",
interfaces: () => [NodeInterface],
isTypeOf: value => value instanceof PurchaseOrderModel,
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: obj => dbIdToNodeId(obj._id, "PurchaseOrder")
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
customer: {
type: CustomerType,
resolve: (source, args, context) => {
return context.customerLoader.load(source.customer_id);
}
}
})
});
export default () => {
return graphqlHTTP((req, res, graphQLParams) => {
return {
schema: schema,
graphiql: true,
pretty: true,
context: {
customerLoader: customerGetByIdsLoader()
},
formatError: error => ({
message: error.message,
locations: error.locations,
stack: error.stack,
path: error.path
})
};
});
};
export const customerGetByIdsLoader = () =>
new DataLoader(ids => {
return customerGetByIds(ids);
});
export const customerGetByIds = async ids => {
let result = await Customer.find({ _id: { $in: ids }, deletedAt: null }).exec();
let rows = ids.map(id => {
let found = result.find(item => {
return item.id.equals(id);
});
return found ? found : null; << === found always undefined
});
return rows;
};
I'm facing the following problems when loading several PurchaseOrders:
A single customer_id is being called more than once in the ids parameter of the DataLoader. So an example id 5cee853eae92f6021f297f45 is being called on several requests to my loader, in successive calls. That suggests that the cache is not working properly.
My found variable when processing the read result is always being set to false, even comparing the right ids.
You can use findOne
export const customerGetByIds = async ids => {
let result = await Customer.find({ _id: { $in: ids }, deletedAt: null }).exec();
const rows = []
let promiseAll = ids.map(async (id) => {
let found = result.filter(item => item.id.toString() === id.toSring());
if(found) {
rows.push(found[0])
return found[0]
}
return null;
});
await Promise.all(promiseAll);
return rows;
};

find all data using same field and different values in mysql sequelize

I need to find the data using same field on mysql,
but different values.
but i geetting empty values in response.
I tried the $and condition to find the data on sequelize.
but it getting not data in response.
exports.multibranchdata = (req, res) => {
const wherequery = {
$and: [{ id: { eq: 1 } },
{ id: { eq: 2 } }
] };
branch.findAll(wherequery)
.then(data => res.status(200).send(data))
.catch(Sequelize.ValidationError, err =>
res.status(422).send(err.errors[0].message))
.catch(err => res.status(400).send(err.message));
};
Try OR query :-
const wherequery = {
$or: [{ id: { eq: 1 } },
{ id: { eq: 2 } }
] };

NodeJS - controller - multiple queries from collections + forEach

Getting messy with promises and async & await. Would like to get your advice, what is the best practice for below controller case? Now it wont get user details before passing data to client.
I am doing a simply API endpoint to server side with NodeJS. Controller is making two queries to complete simply results as json object for client. Yes there is this useless run[] array but it is used when building whole event results :)
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
endtimeid: [],
userid: [],
endtime: [],
username: [],
}]
}
// Run objectid
resultsParams.run[0].runid = req.params.id;
// Get the endtimes by location/stage
Endtime.find({stage: req.params.id})
.then(data => {
// First loop
data.forEach(value => {
resultsParams.run[0].endtimeid.push(value._id);
resultsParams.run[0].userid.push(value.user);
resultsParams.run[0].endtime.push(value.endtime);
})
})
.then(() => {
// Second loop to get user details for results object
resultsParams.run[0].userid.forEach((userId, i) => {
TempUser.findById(userId)
.then(userdetails => {
console.log('userdetails.name', userdetails.name);
resultsParams.run[0].username.push(userdetails.name);
});
})
})
.then(() => {
res.json(resultsParams);
});
}
//////////// client side will get json as follows
{
"run": [
{
"runid": "5ae850d51717862590dc30d4",
"endtimeid": [
"5aec482d98555332145eccd3",
"5aec48c098555332145eccd6",
"5aec4a2c98555332145eccda",
"5aec4ab398555332145eccdd",
"5aec4bb998555332145ecce1",
"5aec4e42c3bcbb196c8474fc",
"5aec4e44c3bcbb196c8474fe",
"5aec4e45c3bcbb196c847500"
],
"userid": [
"5aec13b098555332145eccbe",
"5ae869c797e54a37f498c98f",
"5aec4a1298555332145eccd7",
"5aec4a1298555332145eccd7",
"5aec4ba698555332145eccde",
"5aec13a598555332145eccbc",
"5ae869c797e54a37f498c98f",
"5aec13b098555332145eccbe"
],
"endtime": [
24424,
3280,
11858,
38874,
5738,
40384,
50906,
36717
],
"username": []
}
]
}
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
endtimeid: [],
userid: [],
endtime: [],
username: [],
}]
}
// Run objectid
resultsParams.run[0].runid = req.params.id;
Endtime.find({stage: req.params.id})
.then(data => {
return Promise.all(data.map(record => {
TempUser.findById(record.user)
.then(userdetails => {
return {
endtimeid: record._id,
userid: record.user,
endtime: record.endtime,
username: userdetails.name
};
})
}))
.then(detailRecords => {
return detailRecords.reduce((acc, curr) => {
acc.endtimeid.push(curr.endtimeid);
acc.userid.push(curr.userid);
acc.endtime.push(curr.endtime);
acc.username.push(curr.username);
return acc;
},resultsParams.run[0]);
})
.then(() => {
res.json(resultsParams);
});
});
}
If you have a requirement to accumulate things into the "associative array" style of things in this result object, this is probably the way I would do it.
With that said, I would most likely try to return things as an array of run results - something more like the following...
{
"run": [
{
"runid": "5ae850d51717862590dc30d4",
"results": [
{
"endtimeid": "5aec482d98555332145eccd3",
"userid": "5aec13b098555332145eccbe",
"endtime": 24424,
"username": "User123"
},
{
"endtimeid": "5aec48c098555332145eccd6",
"userid": "5ae869c797e54a37f498c98f",
"endtime": 3280,
"username": "User234"
}
]
}
]
}
I believe the following code should do that.
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
results: []
}]
}
// Run objectid
resultsParams.run[0].runid = req.params.id;
Endtime.find({stage: req.params.id})
.then(data => {
return Promise.all(data.map(record => {
TempUser.findById(record.user)
.then(userdetails => {
return {
endtimeid: record._id,
userid: record.user,
endtime: record.endtime,
username: userdetails.name
};
})
}))
})
.then(results => resultsParams.run[0].results = reuslts)
.then(() => res.json(resultsParams));
}
There could certainly be more readability refactors to be done, and the fact that the last step of populating the result doesn't use the result of the previous flow troubles me a bit - but I try to avoid side effects whenever possible, so that may just be me.
Here is the edited versions as adviced :) But still missing the user details, it looks like promise.all isnt waiting "TempUser.findById(record.user)" query.
1 I created a fresh event, run, 3 users and 3 results.
2 Then I set some logs along db calls
3 Then I did a get request with postman
Log from localhost look like this after Postman GET request:
$ node app
Server started on 5000
Mongodb connected
id 5aeebd8a1b5ddf1424c25194
Get endtimes from database
[ { created: 2018-05-06T08:32:46.359Z,
_id: 5aeebdae1b5ddf1424c25199,
endtime: 23204,
user: 5aeebd751b5ddf1424c25191,
stage: 5aeebd8a1b5ddf1424c25194,
__v: 0 },
{ created: 2018-05-06T08:32:49.414Z,
_id: 5aeebdb11b5ddf1424c2519b,
endtime: 17149,
user: 5aeebd7b1b5ddf1424c25192,
stage: 5aeebd8a1b5ddf1424c25194,
__v: 0 },
{ created: 2018-05-06T08:32:51.769Z,
_id: 5aeebdb31b5ddf1424c2519d,
endtime: 10840,
user: 5aeebd7f1b5ddf1424c25193,
stage: 5aeebd8a1b5ddf1424c25194,
__v: 0 } ]
Set results to object
[ undefined, undefined, undefined ]
This should be the last one at chain?
Get user details from database
{ date: 2018-05-06T08:31:49.673Z,
_id: 5aeebd751b5ddf1424c25191,
name: 'Firstame Lastname1',
__v: 0 }
2. Get user details from database { date: 2018-05-06T08:31:55.562Z,
_id: 5aeebd7b1b5ddf1424c25192,
name: 'Firstame Lastname2',
__v: 0 }
2. Get user details from database { date: 2018-05-06T08:31:59.906Z,
_id: 5aeebd7f1b5ddf1424c25193,
name: 'Firstame Lastname3',
__v: 0 }
That is made by following code:
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
results: []
}]
}
// Each run objectid
resultsParams.run[0].runid = req.params.id;
Endtime.find({stage: req.params.id})
.then(data => {
console.log('1. Get endtimes from database', data);
return Promise.all(data.map(record => {
TempUser.findById(record.user)
.then(userdetails => {
console.log('2. Get user details from database', userdetails);
return {
endtimeid: record._id,
userid: record.user,
endtime: record.endtime,
username: userdetails.name
};
})
}))
})
.then(results => {
console.log('3. Set results to object', results);
console.log('This should be the last one at chain?');
resultsParams.run[0].results = results;
})
.then(() => res.json(resultsParams));
}

Populate array objects from result inside a Function [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 5 years ago.
I need to call the result(array from a function) Called outside the function
My Code look likes this
import axios from 'axios'
var cheats
(() => {
axios.get('api/cheats/getcheaternames')
.then((response) => {
cheats = response.data
console.log(cheats, 'yeah')//results[Good, Bad]
return cheats
})
.catch((error) => {
console.log(error, 'err')
})
})()
const it = [
{
name: ...
}
]
This is how I hardcode the result,
const it = [
{
name: 'Good'
},
{
name: 'Bad'
},
{
name: 'Handsome'
}
]
Any idea to dynamically populate the it variable?
Update
I have no enough reputation to answer my own question, here it is how I refactor my code to make it work
import axios from 'axios'
var cheats
var it = []
let myFirstPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(axios.get('api/cheats/getcheaternames')
.then((response) => {
cheats = response.data
return cheats
})
.catch((error) => {
console.log(error, 'err')
}))
}, 250)
})
myFirstPromise.then((successMessage) => {
// for (var i = 0; i < successMessage.length; i++) {
// it.push({name: i})
// }
for (const value of successMessage) {
it.push({name: value})
}
})
export default {
items: [
{
name: 'Dashboard',
url: '/dashboard',
icon: 'icon-speedometer',
badge: {
variant: 'primary',
text: 'Cheaters'
}
},
{
title: true,
name: 'Elements',
class: '',
wrapper: {
element: '',
attributes: {}
}
},
{
name: 'Cheats',
url: '/components',
icon: 'icon-puzzle',
children: it
},
{
name: 'Angular Version',
url: 'https://angular.herokuapp.com/',
icon: 'icon-cloud-download',
class: 'mt-auto',
variant: 'success'
}
]
}
Any idea to make this code better is appreciated. Am I missing something or am I doing right with this code?
Not entirely clear what you're asking, but if you're asking how to change the items variable, change it to have let instead of const and change your code to:
.then((response) => {
cheats = response.data;
console.log(cheats, 'yeah')//results[Good, Bad]
items = response.data.map(item => ({ name: item }));
return cheats
})

Categories