I am trying to replace the loop that send out http requests using Axios. http is an Axios object and returns a promise. I want to change the code so that I use Promises.all() instead of a loop. I am trying to create a Promise, push into an array and then pass on to Promises.all. I only get empty arrays in my promises array.
I would appreciate any pointers on what I am doing wrong.
// Converting this
responseData = [];
for (const record of response.records) {
let response = await http.get('/records/' + record.id);
responseData.push(response.data.data);
}
// I am trying to convert to this ..
let promises = [];
for (const record of response.data.data) {
let promise = new Promise((resolve, reject) => {
let response = http.get('/records/' + record.id)
.then(response => {
return response.json();
})
.then(resp => {
// console.log(resp.data.data);
//return resp.data.data
resolve(resp.data.data);
});
return response;
});
promises.push(promise);
}
Promise.all(promises).then(records);
Not entirely sure of whether response.json() is required, and where the .records and .data.data should go ... but this might work:
const promises = [];
for (const record of response.records) {
promises.push(
http.get('/records/' + record.id)
.then(response => response.json()) //maybe?
.then(response => response.data.data)
)
}
Promise.all(promises).then(records => { /* do something */ } );
or use map:
const promises = response.records.map(record =>
http.get('/records/' + record.id)
.then(response => response.json()) //maybe?
.then(response => response.data.data)
)
Promise.all(promises).then(responseData => {/* do something */} );
Related
I need to make two calls to two rest api, in the first with one of the data obtained, pass it to the second url and ai obtain what I want, I could achieve it with the code below but my boss tells me that it is wrong, first it I did with asyn await and it told me not to use it, then later with fetch and axios but it is not well written, what would be the correct way to do it with both axios and fetch cases?
with axios
axios.all([axios.get(`${urlRestApi}`)]).then(
axios.spread(response1 => {
let arrPost = response1.data.map(res => {
return {
titulo: res.title.rendered,
contenido: res.content.rendered,
extracto: res.excerpt.rendered,
idImagen: res.featured_media,
};
});
console.log("AQUI", arrPost);
arrImg = arrPost.map(image => {
axios.get(`${urlImage}/${image.idImagen}`)
.then(urls => { // urls returns 10 objects each with a corresponding image
arrUrl.push(urls.data); //for this reason I put all 10 in an array, but it happens 10 times
if (arrUrl.length === 10) { // that's why when .length is 10 I go through it and assign what I want
let arrImage = arrUrl.map(img => {
return {
imagenes: img.source_url,
};
});
console.log("TEST", arrImage);
mostrarHTML(arrImage, arrPost); //I already have everything I run my function to print the data obtained
}
});
});
})
);
and with fetch
fetch(`${urlRestApi}`)
.then(respuesta => {
return respuesta.json();
})
.then(data => {
let arrPost = data.map(data => {
return {
titulo: data.title.rendered,
contenido: data.content.rendered,
extracto: data.excerpt.rendered,
idImagen: data.featured_media,
};
});
console.log(arrPost);
arrImg = arrPost.map(image => {
fetch(`${urlImage}/${image.idImagen}`)
.then(res => {
return res.json();
})
.then(urls => { // // urls returns 10 objects each with a corresponding image
arrUrl.push(urls); //for this reason I put all 10 in an array, but it happens 10 times
if (arrUrl.length === 10) { // that's why when .length is 10 I go through it and assign what I want
arrImage = arrUrl.map(image => {
return {
imagenes: image.source_url,
};
});
console.log("aqui", arrImage);
mostrarHTML(arrImage, arrPost); //I already have everything I run my function to print the data obtained
}
});
});
})
.catch(error => {
console.log(error);
});
With fetch, without async/await:
fetch(urlRestApi)
.then((respuesta) => respuesta.json())
.then((data) => {
const posts = data.map((data) => ({
titulo: data.title.rendered,
contenido: data.content.rendered,
extracto: data.excerpt.rendered,
idImagen: data.featured_media,
}));
// Create an array of promises that fetch image data and combines it
// with the original post.
const imagePostPromises = posts.map((post) => {
return fetch(`${urlImage}/${image.idImagen}`)
.then((res) => res.json())
.then((imageData) => ({
// Combine the original post with the image data fetched
...post,
imageData,
}));
});
// Return a promise that resolves only when all of the `imagePostPromises` have finished.
return Promise.all(imagePostPromises);
})
.then((postsWithImages) => {
console.log(postsWithImages);
});
And, much more readably, if only you could use async/await,
async function doThings() {
const respuesta = await fetch(urlRestApi);
const data = await respuesta.json();
const posts = data.map((data) => ({
titulo: data.title.rendered,
contenido: data.content.rendered,
extracto: data.excerpt.rendered,
idImagen: data.featured_media,
}));
const imagePostPromises = posts.map(async (post) => {
const res = await fetch(`${urlImage}/${image.idImagen}`);
const imageData = await res.json();
// Combine the original post with the image data fetched
return ({
...post,
imageData,
});
});
const postsWithImages = await Promise.all(imagePostPromises);
console.log(postsWithImages);
}
I've got the following code that retrieves data asynchronously and caches it for performance optimization purposes:
let cache = {}
const optimizedFetch = async (url) => {
if (url in cache) {
// return cached result if available
console.log("cache hit")
return cache[url]
}
try {
const response = await fetch (url)
const json = response.json();
// cache response keyed to url
cache[url] = json
return json
}
catch (error) {
console.log(error)
}
}
optimizedFetch("https://jsonplaceholder.typicode.com/todos").then(console.log)
The approach above works fine but if a second request to the same url comes while the first one is still awaited then a second fetch will be fired.
Could you please advice me on the ways to improve that scenario?
Thanks in advance.
let cache = {};
let awaits = {};
const optimizedFetch = (url) => {
return new Promise((resolve, reject) => {
if (url in cache) {
// return cached result if available
console.log("cache hit");
return resolve(cache[url]);
}
if (url in awaits) {
console.log("awaiting");
awaits[url].push({
resolve,
reject
});
return;
}
console.log("first fetch");
awaits[url] = [{
resolve,
reject
}];
fetch(url)
.then(response => response.json())
.then(result => awaits[url].forEach(({
resolve
}) => resolve(result)))
.catch(error => awaits[url].forEach(({
reject
}) => reject(error)))
.finally(() => {
delete awaits[url];
});
});
};
optimizedFetch("https://jsonplaceholder.typicode.com/todos")
.then(({
length
}) => console.log(length));
optimizedFetch("https://jsonplaceholder.typicode.com/todos")
.then(({
length
}) => console.log(length));
Just save Promise as a value.
Working example:
let cache = {}
let totalRequests = 0;
const optimizedFetch = async(url) => {
if (cache[url]) return cache[url]; // return what we have
totalRequests++;
const fetchedRequest = fetch(url)
.then(response => response.json())
.then(json => {
cache[url] = json; // <-- we replace "Promise" result with json result
return json;
});
cache[url] = fetchedRequest; // <-- we keep Promise as a result
return fetchedRequest;
}
for (let i = 0; i < 5; i++) {
console.log('Setting request #', i);
optimizedFetch("https://jsonplaceholder.typicode.com/todos").then(result => console.log('Requests:', totalRequests, 'Result:', result.length));
}
Explanation:
totalRequests is just for testing purposes - it shows us how many "real" fetches we made
if we have an answer in cache - we return that answer
if we do not have answer - we create new fetch and put it as answer
P.S. My answer is getting downvotes, but I do not understand why... It is working cache example. Please, people, if you downvote, comment above, so I will understand my mistake if any. Thank you
I have the following code that is used to get JSON data from an Amazon Web Server API.
var json1 = new Promise((resolve, reject) => {
fetch(url[0])
.then(r => {
resolve(r.json())
})
.catch(err => {
reject(err)
})
})
I have this repeating 14 times using different urls and json vars and have it return the promises at the end using.
return Promise.all([json1,json2,json3,json4,json5,json6,json7,json8,json9,json10,json11,json12,json13,json14]).then(function(values) {
return values;
});
This works, but it takes up 150+ lines. I want to make a for loop that runs through the same code using a for loop. I created this...
for(var jsonCount = 0;jsonCount<url.length-1;jsonCount++){
jsonArr[jsonCount] = new Promise((resolve, reject) => {
fetch(url[jsonCount])
.then(r => {
resolve(r.json())
})
.catch(err => {
reject(err)
})
})
}
This doesn't work because the promise functions come back as undefined even though it is called by an await function.
const data = await fetchURL(urlToQuery())
Does anyone have suggestions to make this work? There is JSON being returned.
Thanks for your help.
Edit: Here is a larger chunk of the code.
function fetchURL(urls) {
let fetchJson = url => fetch(url).then(response => response.json());
Promise.all(urls.map(fetchJson)).then(arr => {
return arr;
});
(async function() {
const data = await fetchURL(urlToQuery())
console.log(data);
for(var r=0;r<numStations;r++){
if (data[r] == ""){
onlineArr[r] = false;
wdDataArr[r].push(cardinalToDeg(stationHistAvgArr[r]));
wsDataArr[r].push(0);
You can use .map for the loop. But don't use new Promise. You don't need a new promise when fetch already provides you with one.
Also, call your array urls instead of url. A plural will be a good indication for the reader of your code that indeed it is a collection of URLs.
Here is how it could look:
let fetchJson = url => fetch(url).then(response => response.json());
Promise.all(urls.map(fetchJson)).then(arr => {
// process your data
for (let obj of arr) {
console.log(obj);
}
});
I think this example can helps you:
// Mock async function
const getDataAsync = callback => {
setTimeout(
() => callback(Math.ceil(Math.random() * 100)),
Math.random() * 1000 + 2000
)
}
// Create the promise
const getDataWithPromise = () => {
return new Promise((resolve, reject) => {
try {
getDataAsync(resolve);
} catch(e) {
reject(e);
}
});
}
// Using the promise one time
getDataWithPromise()
.then(data => console.log("Simple promise:",data))
.catch(error => console.error(`Error catched ${error}`));
// Promises compound: Promise.all
const promise1 = getDataWithPromise();
promise1.then(data => console.log("promise1 ends:",data));
const promise2 = getDataWithPromise();
promise2.then(data => console.log("promise2 ends:",data));
const promise3 = getDataWithPromise();
promise3.then(data => console.log("promise3 ends:",data));
const promise4 = getDataWithPromise();
promise4.then(data => console.log("promise4 ends:",data));
const promise5 = getDataWithPromise();
promise5.then(data => console.log("promise5 ends:",data));
Promise.all([promise1,promise2,promise3,promise4,promise5])
.then(data => console.log("Promise all ends !!",data));
Hope this helps
you will have issues with closure and var variable capture.
You may want to change var to let to capture the right value in the closure so that url[jsonCount] is actually what you want.
also I think it would be much easier to do something like that in one line :)
let results = [];
for(let i = 0; i < urls.length; ++i) results.push(await (await fetch[urls[i]]).json());
This is a good use for map, mapping urls to promises...
function fetchUrls(urls) {
let promises = urls.map(url => fetch(url))
return Promise.all(promises).then(results => {
return results.map(result => result.json())
})
}}
// url is your array of urls (which would be better named as a plural)
fetchUrls(url).then(results => {
// results will be the fetched json
})
Using the async/await syntax (equivalent meaning)
// this can be called with await from within another async function
async function fetchUrls(urls) {
let promises = urls.map(url => fetch(url))
let results = await Promise.all(promises)
return results.map(result => result.json())
}
I'm importing a json file and loop through the object.
Each object has an array with eventIDs, which I loop through as well.
In this forEach I push a wrapper function that returns a Promise into an array promises
After looping through all objects I perform a promise.all() on the promises array. But strangely anything I do after this loop is not executed.
const fs = require('fs')
const promisify = require('util').promisify
const rp = require('request-promise')
const readFile = promisify(fs.readFile)
const filePath = 'json/results.json'
let promises = []
function init() {
readFile(filePath, 'utf8')
.then(file => {
const json = JSON.parse(file)
for (const country of json) {
if (country.eventIDs.length === 0) return
country.eventIDs.forEach(id => promises.push(getEventData(country, id)))
}
// nothing here is executed,
console.log('this is not fired')
Promise.all(promises)
.then(results => writeFile(results))
.catch( err => console.log(err))
})
.catch(err => console.log(err))
}
getEventData = (country, id) => new Promise((resolve, reject) => {
setTimeout(() => {
rp(url)
.then((results) => {
resolve ({
...results
})
})
.catch((err) => reject(`getEventData Error:\n ${err}`))
}, 2000)
})
writeFile = (results) => {
const json = JSON.stringify(results)
// const date = new Date()
// const filename = `${date.getTime()}-all-event-ids.json`
const filename = `results-allevents.json`
fs.writeFile(`json/${filename}`, json, 'utf8', () => console.log(`Succesfully written: ${filename}`))
}
init()
After the investigation, the line:
if (country.eventIDs.length === 0) return
was the main issue (and many others solved through comments).
The issue, to explain that, is that the return is not skipping the looped item (as perhaps expected), but rather returning void in the then callback, so skipping further executions of that block.
In order to "skip" the item if that condition is true, just do this instead
for (const country of json) {
if (country.eventIDs.length > 0) {
country.eventIDs.forEach(id => promises.push(getEventData(country, id)))
}
}
better is to capture each result from each promise. Because if you want array of responses of the .All . If one of those promises reject you will get no results.
const results = [];
country.eventIDs.forEach(id => promises.push(getEventData(country, id)
.then(res => results.push(res));
Promise.all(promises).then(()=> writeFile(results))
Stackblitz Example
I Have a array of objects which i need to clone with different values.
Those values i'll get from each promise finally after preparing main modified array of object i'll have to save this. So i many need this as one single promise.
I not sure how to do it.
Here is the example we need to clone oldUser data. Say old user has credit score = 100; but for new user default credit will be created randomly by system.
For each user in the array of users few details has to get updated using async call.
This is the requirement
function getUserCreditScore(user){
var url = '/someurl';
return $http.get(url).then(function(res){
user.creditScore = (res.data) ? res.data : 0;
});
}
function getUserRecomandations(user){
var url = '/someurl';
return $http.get(url).then(function(res){
user.recommendation = (res.data) ? res.data : 'basic recommendation';
});
}
function getUserHelpInfo(user){
var url = '/someurl';
return $http.get(url).then(function(res){
user.helpInfo = (res.data) ? res.data : 'Help Info';
});
}
function clone(){
var newUsers = angular.copy(oldUsers);
for (var i=0; i<newUsers.length; i++){
newUsers[i].id = undefined;
getUserCreditScore(newUsers[i]);
getUserRecommendation(newUsers[i]);
getUserHelpInfo(newUsers[i]);
}
var promises = _.map(newUsers, user => user.save());
$q.all(promises).then(function (data) {
console.log(data);
}
}
You'll need to Promise.all on an array of Promises that are returned by getScreditScore
something like
function getCreditScore(){
var url = '/someurl';
return $http.get(url).then(res => (res && res.data) ? res.data : res);
}
function clone(){
var newUsers = angular.copy(oldUsers);
Promise.all(
newUsers.map(newUser => {
newUser.id = undefined;
return getCreditScore()
.then(result => newUser.creditScore = result);
})
).then(results => // results will be an array of values returned by the get in getCreditScore(newUser)
Promise.all(newUsers.map(user => user.save()))
).then(data =>
console.log(data); // this will be the result of all the user.save
);
}
Note: the newUser.creditScore is set in the .then in the newUsers.map callback - (minimal change to my original answer)
Alternatively, passing user to getCreditScore
function getCreditScore(user){
var url = '/someurl';
return $http.get(url)
.then(res => (res && res.data) ? res.data : res)
.then(score => user.creditScore = score);
}
function clone(){
var newUsers = angular.copy(oldUsers);
Promise.all(
newUsers.map(newUser => {
newUser.id = undefined;
return getCreditScore(newUser);
})
).then(results => // results will be an array of values returned by the get in getCreditScore(newUser)
Promise.all(newUsers.map(user => user.save()))
).then(data =>
console.log(data); // this will be the result of all the user.save
);
}
Personally, I'd write the code
function getCreditScore(){
var url = '/someurl';
return $http.get(url).then(res => (res && res.data) ? res.data : res);
}
function clone(){
var newUsers = angular.copy(oldUsers);
Promise.all(
newUsers.map(newUser => {
newUser.id = undefined;
return getCreditScore()
.then(result => newUser.creditScore = result)
.then(() => newUser.save())
.then(() => newUser);
})
).then(data =>
console.log(data); // this will be the newUsers Array
);
}
This assumes, though, that you don't need to wait for all the $http.get before running the user.save() - in fact this may be a little (very little) more performant as the newUser.save and $http.get will run in tandem
Ok, I know your meaning, you want your every element of your array do something that is async.
So you can use map and Promise.all. Here is my code:
const asyncFunction = (item, cb) => {
setTimeout(() => {
console.log(`done with ${item}`);
cb();
}, 1000);
}
let requests = [1, 2, 3].map((item) => {
return new Promise((resolve) =>{
asyncFunction(item, resolve);
});
});
Promise.all(requests).then(() => console.log('done'));