Array remains empty after push - javascript

I declared an array, But when I push elements inside it, it remains Empty. Here's my Code :
var catsObjectId = new Array();
var data = new Array();
Recipe.find((err,doc3)=> {
data = doc3;
for (var i = 0; i < data.length; i++) {
catsObjectId.push([]);
data[i]['categories'].forEach((item, index) => {
Recipecat.findOne({_id: item}, (err,result)=> {
item = result.name;
catsObjectId.push(item);
});
})
}
console.log(catsObjectId);
});
Here's the Recipe schema :
var recipeSchema = Schema({
categories: [{
type: Schema.Types.ObjectId,
ref: 'RecipeCat',
}]
});
and Here's the Recipecat schema :
var recipecatSchema = new Schema({
name: {
type: String,
required: true
}
});
I want to replace objectIds for recipeCats with their names.
When I log 'catsObjectId', It shows an empty array.
What Seems to be the Problem?
Thanks In advance!

(I understand this question is a bit old, but if you still need help)
That's because you're pushing to an array which is outside the callback and the async nature of JavaScript kicking in.
Here's simple explanation why it's empty
var catsObjectId = new Array();
var data = new Array();
Recipe.find((err,doc3)=> {
// say execution 1
for (var i = 0; i < data.length; i++) {
catsObjectId.push([]);
data[i]['categories'].forEach((item, index) => {
// say execution 2
Recipecat.findOne({_id: item}, (err,result)=> {
item = result.name;
catsObjectId.push(item);
});
})
}
// say execution 3
console.log(catsObjectId);
});
First execution 1 is executed. Within this forEach iterates over each item and fires execution 2. Then continues to execute execution 3.
The problem is execution 2 is asynchronous and the value is returned sometime in the future. This future is after excution 3 is executed. When Recipecat.findOne finishes execution, the callback within then(result.. is called. But console.log(catsObjectId) is already executed and catsObjectId was empty at the time of execution.
You should either use catsObjectId within the callback .then((data) => // use data here) or use the async/await to make it sync like.
Note await is only valid inside async function
async function getSomeNames() {
try {
const data = await Recipe.find();
// docs is an array of promises
const docs = data.map((item, index) => {
Recipecat.findOne({_id: item})
});
// items is an array of documents returned by findOne
const items = await Promise.all(docs);
// now you can map and get the names
const names = items.map(item => item.name);
} catch (e) {
// handle error
console.error(e);
}
}
getSomeNames()

Your pushing an empty array every time it goes through the for loop. Try deleting this line.
catsObjectId.push([]);

You have to use promises in order to control your code. Try the following code and tell me if an error exists.
Recipe.find().then(doc3 => {
data = doc3;
for (var i = 0; i < data.length; i++) {
data[i]['categories'].forEach((item, index) => {
Recipecat.findOne({_id: item}).then(result => {
item = result.name;
catsObjectId.push(item);
});
})
}
console.log(catsObjectId);
})
.catch(err => {
console.log(err);
});

Recently ran into a similar problem. Fix for me was to replace the forEach loop with a simple for loop. It turned out, that the forEach loop is not bothering about async-await, but the for loop is.
Here is my code snippet:
let orders = await order_db.find({ profileID: req.body.id }).exec();
let final_orders = [];
for(let i=0; i<orders.length; i++){
let order = orders[i];
if (order.shopID != null) {
let shop = await shop_db.find({ _id: order.shopID }).exec();
let shopName = shop[0].name;
let shopEmail = shop[0].email;
let shopAddress = shop[0].address;
let shopPhone = shop[0].phone;
let updated_order = { ...order._doc, shopName, shopEmail, shopAddress, shopPhone };
final_orders.push(updated_order);
}
else {
let shopName = "";
let shopEmail = "";
let shopPhone = "";
let shopAddress = "";
let updated_order = { ...order._doc, shopName, shopEmail, shopAddress, shopPhone };
final_orders.push(updated_order);
}
};

Related

JavaScript: How can I insert an Object into an Array of Objects?

I have this script that takes data from a JSON with almost 100 data, then uses this data to bring the weather from an API and after that, inserts this data into an object (using a for for creating my 100 objects), I would like to add the objects that have a temperature > 99 in one array and the ones that have a temperature < 99 into another I have tried this way but doesn't seem to work, sorry if it's a super fool mistake that I can't see, thanks for your help!
This is my script:
async function calcWeather(){
const info = await fetch('../json/data.json')
.then(function(response) {
return response.json()
});
for (var i in info) {
const _idOficina = info[i][0].IdOficina
const _nombreOficina = info[i][0].NombreOficinaSN
const _zona = info[i][0].Zona
const _estado = info[i][0].NombreEstado
const lat = info[i][0].latjson
const long = info[i][0].lonjson
const base = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${long}&appid=${api_key}&units=metric&lang=sp`
fetch(base)
.then((responses) => {
return responses.json()
})
.then((data) => {
// console.log(data)
var myObject = {
Id_Oficina: _idOficina,
Latitud: data.coord.lat,
Longitud: data.coord.lon,
Ciudad: data.name,
Estado: _estado,
Zona: _zona,
Nombre_Oficina: _nombreOficina,
Temperatura: data.main.temp,
Descripcion: data.weather[0].description
};
// validation
if (myObject.Temperatura < 99){
var lstValid = [];
function pushValid(){
lstValid.push(myObject[i]);
}
pushValid();
console.log(pushValid())
}
});
}
};
Your array is local, so for every object you create new lstValid array with no previous data. The solution is to create the array before fetching the data or before the loop:
async function calcWeather(){
var lstValid = []; // HERE
const info = await fetch('../json/data.json')
.then(function(response) {
return response.json()
});
var lstValid = []; // OR HERE (ONLY ONE OF THEM)
for (...) {
...
}
You'll probably be best served by creating the array outside of that call since you're clearing it every run. Then simply add your object. Like Trincot's comment, i'm not sure what exactly you're indexing.
async function calcWeather(){
var lstValid = [];
....
if (myObject.Temperatura < 99){
lstValid[someindex] = myObject;
}
else{
lstNotValid[someOtherIndex] = myObject;
}
}

async functions not executing in the correct order inside a map function

I have created an async function that will extra the data from the argument, create a Postgres query based on a data, then did some processing using the retrieved query data. Yet, when I call this function inside a map function, it seemed like it has looped through all the element to extra the data from the argument first before it proceed to the second and the third part, which lead to wrong computation on the second element and onwards(the first element is always correct). I am new to async function, can someone please take at the below code? Thanks!
async function testWeightedScore(test, examData) {
var grade = [];
const testID = examData[test.name];
console.log(testID);
var res = await DefaultPostgresPool().query(
//postgres query based on the score constant
);
var result = res.rows;
for (var i = 0; i < result.length; i++) {
const score = result[i].score;
var weightScore = score * 20;
//more computation
const mid = { "testID": testID, "score": weightScore, more values...};
grade.push(mid);
}
return grade;
}
(async () => {
const examSession = [{"name": "Sally"},{"name": "Bob"},{"name": "Steph"}]
const examData = {
"Sally": 384258,
"Bob": 718239,
"Steph": 349285,
};
var test = [];
examSession.map(async sesion => {
var result = await testWeightedScore(sesion,examData);
let counts = result.reduce((prev, curr) => {
let count = prev.get(curr.testID) || 0;
prev.set(curr.testID, curr.score + count);
return prev;
}, new Map());
let reducedObjArr = [...counts].map(([testID, score]) => {
return {testID, score}
})
console.info(reducedObjArr);
}
);
})();
// The console log printed out all the tokenID first(loop through all the element in examSession ), before it printed out reducedObjArr for each element
The async/await behaviour is that the code pause at await, and do something else (async) until the result of await is provided.
So your code will launch a testWeightedScore, leave at the postgresql query (second await) and in the meantime go to the other entries in your map, log the id, then leave again at the query level.
I didn't read your function in detail however so I am unsure if your function is properly isolated or the order and completion of each call is important.
If you want each test to be fully done one after the other and not in 'parallel', you should do a for loop instead of a map.

Push inside forEach with query not working properly

I'm working with mongodb stitch/realm and I'm trying to modify objects inside an array with a foreach and also pushing ids into a new array.
For each object that i'm modifying, I'm also doing a query first, after the document is found I start modifying the object and then pushing the id into another array so I can use both arrays later.
The code is something like this:
exports = function(orgLoc_id, data){
var HttpStatus = require('http-status-codes');
// Access DB
const db_name = context.values.get("database").name;
const db = context.services.get("mongodb-atlas").db(db_name);
const orgLocPickupPointCollection = db.collection("organizations.pickup_points");
const orgLocStreamsCollection = db.collection("organizations.streams");
const streamsCollection = db.collection("streams");
let stream_ids = [];
data.forEach(function(stream) {
return streamsCollection.findOne({_id: stream.stream_id}, {type: 1, sizes: 1}).then(res => { //if I comment this query it will push without any problem
if(res) {
let newId = new BSON.ObjectId();
stream._id = newId;
stream.location_id = orgLoc_id;
stream.stream_type = res.type;
stream.unit_price = res.sizes[0].unit_price_dropoff;
stream._created = new Date();
stream._modified = new Date();
stream._active = true;
stream_ids.push(newId);
}
})
})
console.log('stream ids: ' + stream_ids);
//TODO
};
But when I try to log 'stream_ids' it's empty and nothing is shown. Properties stream_type and unit_price are not assigned.
I've tried promises but I haven't had success
It's an asynchronous issue. You're populating the value of the array inside a callback. But because of the nature of the event loop, it's impossible that any of the callbacks will have been called by the time the console.log is executed.
You mentioned a solution involving promises, and that's probably the right tack. For example something like the following:
exports = function(orgLoc_id, data) {
// ...
let stream_ids = [];
const promises = data.map(function(stream) {
return streamsCollection.findOne({ _id: stream.stream_id }, { type: 1, sizes: 1 })
.then(res => { //if I comment this query it will push without any problem
if (res) {
let newId = new BSON.ObjectId();
// ...
stream_ids.push(newId);
}
})
})
Promise.all(promises).then(function() {
console.log('stream ids: ' + stream_ids);
//TODO
// any code that needs access to stream_ids should be in here...
});
};
Note the change of forEach to map...that way you're getting an array of all the Promises (I'm assuming your findOne is returning a promise because of the .then).
Then you use a Promise.all to wait for all the promises to resolve, and then you should have your array.
Side note: A more elegant solution would involve returning newId inside your .then. In that case Promise.all will actually resolve with an array of the results of all the promises, which would be the values of newId.

Pushing data in array inside for loop

Thanks in advance, i am trying to add request response in promises array inside for loop, currently CURRENCIES array have two values and forloop working good iterating for two times, but inside push i am unable to get value of CURRENCIES array at current index.
let promises = [];
for (var i = 0; i < CURRENCIES.length; i++) {
const api = 'https://api.etherscan.io/api';
promises.push(
axios.get(api, {
params: {
module: 'account', action: 'txlist',
address: '0x.........',
sort: 'asc', apikey: 'R6...........'
}
}).then((response) => {
console.log(CURRENCIES[i])
var data = {};
data[CURRENCIES[i]] = response.data.result
transactionsHistory.push(data);
})
)
}
In console.log for CURRENCIES[i] i always have undefined
The value of i is changing by the time the Promise resolves and you try to use it in the then function. You can be more explicit with it like this:
let promises = [];
for (var i = 0; i < CURRENCIES.length; i++) {
const api = "https://api.etherscan.io/api";
// setting a variable within the loop will make it valid within the scope.
const currency = CURRENICES[i];
promises.push(
axios
.get(api, {
params: {
module: "account",
action: "txlist",
address: "0x.........",
sort: "asc",
apikey: "R6..........."
}
})
.then(response => {
console.log(currency);
var data = {};
data[currency] = response.data.result;
transactionsHistory.push(data);
})
);
}
The problem was that you were using var which makes it a kind of global variable at the top level scope. Changing to let would make its scope more localized and should solve the problem too, but I like using a different variable within the loop to be more clear what is being used.

React Axios API call with array loop giving wrong order?

I was learning react and doing some axios api call with an array. I did a code on gathering data through coinmarketcap api to learn.
So, my intention was to get the prices from the api with a hardcoded array of cryptocurrency ids and push them into an array of prices. But I ran into a problem with the prices array, as the prices were all jumbled up. I was supposed to get an array in this order
[bitcoinprice, ethereumprice, stellarprice, rippleprice]
but when I ran it in the browser, the prices came randomly and not in this order, sometimes I got my order, sometimes it didn't. I used a button which onClick called the getPrice method. Does anyone know what went wrong with my code? Thanks!
constructor(){
super();
this.state = {
cryptos:["bitcoin","ethereum","stellar","ripple"],
prices:[]
};
this.getPrice = this.getPrice.bind(this);
}
getPrice(){
const cryptos = this.state.cryptos;
console.log(cryptos);
for (var i = 0; i < cryptos.length; i++){
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
axios.get(cryptoUrl)
.then((response) => {
const data = response.data[0];
console.log(data.price_usd);
this.state.prices.push(data.price_usd);
console.log(this.state.prices);
})
.catch((error) => {
console.log(error);
});
}
}
If you want to receive the data in the order of the asynchronous calls you make, you can use Promise.all, that waits until all the promises of an array get executed and are resolved, returning the values in the order they were executed.
const cryptos = ['bitcoin', 'ethereum', 'stellar', 'ripple'];
const arr = [];
for (var i = 0; i < cryptos.length; i++){
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
arr.push(axios.get(cryptoUrl));
}
Promise.all(arr).then((response) =>
response.map(res => console.log(res.data[0].name, res.data[0].price_usd))
).catch((err) => console.log(err));
You could use a closure in the for loop to capture the value of i and use it as the index once the data is returned rather than using push:
getPrice(){
const cryptos = this.state.cryptos;
console.log(cryptos);
for (var i = 0; i < cryptos.length; i++) {
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
(function (x) {
axios.get(cryptoUrl)
.then((response) => {
const data = response.data[0];
console.log(data.price_usd);
var newPrices = this.state.prices;
newPrices[x] = data.price_usd;
this.setState({prices: newPrices});
console.log(this.state.prices);
})
.catch((error) => {
console.log(error);
});
})(i);
}
}

Categories