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.
Related
Context: I'm fetching 'car' data below (see the code that starts with "for") from a GET request and am pushing it to the 'array' array. And for each car.ID that I get, I need to run another GET request in sequence (the GET uses car.ID as a parameter and I have no problems in doing this).
Problem: after I fetch the results from the second GET, how to push the data to the same object of the array (i.e. I want to "complement" the object above that ended on car.BrandID by adding a few more key: value pairs to the same "line")?
THANK YOU IN ADVANCE.
for (let car of carsJustObtained) {
for (i=0; i<=2; i++){
array.push(
{
timestamp: epoch,
ID : car.ID,
BrandID : car.BrandID
})
//code continues but don't worry
FULL CODE BELOW:
function gotCars(carsJustObtained) {
for (let car of carsJustObtained) {
for (i=0; i<=2; i++){
array.push(
{
timestamp: epoch,
ID : car.ID,
BrandID : car.BrandID,
ModelID : car.ModelID,
}
);
//given car.ID the second GET will be triggered because the path depends on this variable!
let path_get_all_prices = `xxx=${car.ID}?securityToken=xxx&vehiclePriceTypeID=xxx`;
let get = https.get(
{
hostname: 'xxx.com',
path: path_get_all_prices
},
(getRes) => {
console.log(`getting prices for car ${car.ID}...`);
var reply = "";
getRes.on("data", (chunk) => (reply += chunk));
const obj = JSON.parse(reply);
gotPrices(obj.Response);
}
);
function gotPrices(pricesJustObtained) {
for (let price of pricesJustObtained){
array.push(
//how to add results to the same array of the 1st GET? There are three 'prices' for each car.ID
)};
};
};
You have to find the index of your object in your array, then you can add everything you can to this object :
array[index].name = 'Hello';
There are many ways to do this. I recommend you read about array.map()
This function lets you iterate your array and in each iteration perform the get request and extend the current element.
The key is to recognise that you are not pushing, the second time
What you are doing is reading each element of the array, and adding some information to that element.
for (let car of carsJustObtained) {
const newInfo=getFurtherInformationAboutCar(car) // this is your second getting
car.newInfo1 = newInfo.param1;
car.newInfo2 = newInfo.param2;
car.newInfo3 = newInfo.param3;
}
To answer your specific question about "merging" information
If you have one set of properties already defined for the car, and you want to merge in multiple new properties, a simple way to do it is as follows:
car = { ...car, ...objectContainingNewProperties};
If your original car was {a:2, b:3, c:4} and objectContainingNewProperties was {c: 10, d:20, e:30}, the result would be:
{ a:2,
b:3,
c:10,
d:20,
e:30 }
Any same-named properties in the second object will overwrite those in the original object.
Your second request is of course asynchronous, so by the time you get its response, you have already populated your array with all information from the first request.
I would suggest to use a promise-enabled alternative to http.get, as promises are a native feature in JavaScript that makes working with asynchronous events less messy. I will show here how it can work with node-fetch.
As fetch is natively supported in browser agents, you can run the snippet below to see the result. As a demo I have used https://jsonplaceholder.typicode.com/ as a server resource: it returns JSON for several sample datasets, including todos and users. A todo has some properties (like a title) and has a user id. A user has an email and a username. So we could make the todos-request the first request, and the users-request the second one (based on the user id received in the first). So the principle is the same as with your cars and prices.
This relies heavily on promises:
// For demo, we use these two URls:
// They both need a number following it
let url1 = "https://jsonplaceholder.typicode.com/todos/";
let url2 = "https://jsonplaceholder.typicode.com/users/";
let promises = [];
// Let's say we build an array with 5 objects:
for (let i = 1; i <= 5; i++) {
promises.push(
// Make the request
fetch(url1 + i*30)
// Parse the response as JSON
.then(resp => resp.json())
// Process this data
.then(data => {
// Create our own object from this data
let obj = {
user: data.userId,
todo: data.title
};
// Make second request, to get user's email, joining it with obj
return Promise.all([obj, fetch(url2 + obj.user)])
})
.then(([obj, resp2]) => Promise.all([obj, resp2.json()]))
// Merge the new data with the old
.then(([obj, data2]) => Object.assign(obj, {
email: data2.email,
logon: data2.username
}))
);
}
// Wait for all requests to finish...
Promise.all(promises).then(results => {
console.log(results); // The result!
});
With async/await
The above can be made even more readable, if you use the async/await syntax:
let url1 = "https://jsonplaceholder.typicode.com/todos/";
let url2 = "https://jsonplaceholder.typicode.com/users/";
async function getOne(i) {
let resp = await fetch(url1 + i*30);
let data = await resp.json();
// Create object from first request
let obj = {
user: data.userId,
todo: data.title
};
// Make second request, joining it with obj
let resp2 = await fetch(url2 + obj.user);
let data2 = await resp2.json();
return Object.assign(obj, {
email: data2.email,
logon: data2.username
});
}
let promises = [];
for (let i = 1; i <= 5; i++) {
promises.push(getOne(i));
}
Promise.all(promises).then(results => {
console.log(results);
});
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.
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);
}
};
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);
}
}
I am building a vue app that will search YouTube channels based on which options are selected.
When the option is TRUE, I push that string into an array which holds the URLs of the axios.get() requests.
I am looping through that array and running axios.get() and returning the value. I am getting a response under Promise{} with and object inside [[PromiseValue]].
At the end I am combining the responses into a vue data element(catAndDogResults) but I am getting an undefined in the end.
Is there a better way to do what I am trying to do?
data () {
return {
catVideos: true,
dogVideos: true,
catResults: [],
dogResults: [],
catAndDogResults: []
}
},
methods:
search: function() {
var that = this
var cats = 'https://www.googleapis.com/youtube/v3/search?part=snippet&q=cats'
var dogs = 'https://www.googleapis.com/youtube/v3/search?part=snippet&q=dogs'
var urls = []
if (this.catVideos == true) {
urls.push(cats)
}
if (this.dogVideos == true) {
urls.push(dogs)
}
function getVideos() {
for (var i = 0; i < urls.length; i++) {
console.log(axios.get(urls[i])) // returns under [[PromiseValue]]:object
return axios.get(urls[i])
}
}
axios.all([
getVideos()
])
.then(axios.spread(function (catRes, dogRes) {
that.catResults = catRes.data.items
that.dogResults = dogRes.data.items
that.catAndDogResults = that.catResults.concat(that.dogResults)
}))
}
EDITS
Fixed spelling mistakes
Try changing your getVideos() method to return the array after the for loop:
function getVideos() {
var requests = [];
for (var i = 0; i < urls.length; i++) {
requests.push(axios.get(urls[i]));
}
return requests;
}
And then, call it like that:
axios.all(getVideos())
.then(function (catRes, dogRes) { ... })
The answer provided by #tiagodws should fix the issue. I just wanted to rewrite the getVideos function, you could write it using ES6 syntax like the following:
var getVideos = () => urls.map(url => axios.get(url))