Asynchronous filtering an array of Promises - javascript

I have an array of strings let symbols = ['abc', 'cde', 'edf', 'qqe', 'hrt'] that I pass as an argument to the function:
async function fetchDetails(symbols) {
let promises = symbols.map((s, i) => {
return fetch(URL + s)
.then(res => return res.json())
.then(json => Object.assign({}, { [s]: json }))
});
console.log('promise:', promises)
return Promise.all(promises);
}
I fetch data from URL/'abc', URL/'cde' etc. and save it into promises array.
But there is an % probability that from the server I will not get all 5 resolved objects. Then in console.log the promises array looks like this:
And I would like to have array containg only 4 resolved items (that I pass to the Promise.all(), instead of 5 (including 4 resolved and 1 with pending status).
If it was a synchronous function, I would simply have to have filtered the array. But I have no access to the [[PromiseStatus]] properties and have no idea how to do this.
Since I am quite new to the Javascript I would appreciate any help with this Async thing. Any scrap or code, or advise where to search for the answer :)
Edit:
Maybe this will help a bit, the route I send a query to, is built like this:
app.get('/data/:symbol', async (req, res) => {
const { params: { symbol } } = req
const data = await stocks.getData(symbol, new Date())
res.send(data)
})
So in the case of error, it doesn't send any error right?
And that's why I could potentially have Pending Status instead of Reject?
SOLUTION OF THE TASK
Hey guys, so I solved this issue with 2 things:
1. Thanks to #Bergi, who pointed to the fact that Pending Status is not something that can be omitted - I checked server side and there was a first problem - Errors were not handled.
2. Then after fixing Server side, since I could separate Resolved Promises from Rejected - I was abble to return Array containing only Resolved promises - using this custom Promise_all solution: https://stackoverflow.com/a/46024590
So my final code looks something like this:
async function fetchDetails(symbols) {
let promises = symbols.map(async (s, i) => {
return await fetch(URL + s)
.then((res)=> {
if (!res.ok) {
throw new Error('Error with fetch')
} else {
return res.json();
}
})
.then(json => Object.assign({}, { [s]: json }))
.catch(err => {
return Promise.reject()})
});
const Promise_all = promises => {
return new Promise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((promise, idx) => {
promise
.catch(err => {
return err;
})
.then(valueOrError => {
results[idx] = valueOrError;
count += 1;
if (count === promises.length) resolve(results);
});
});
});
};
const results = await Promise_all(promises)
const validResults = results.filter(result => result !== undefined);
return validResults;
}
Thank you very much to everyone who was writing here!

If you want to fail after a certain timeout you could do this:
const failIn = milliseconds =>
setTimeout(
_=>Promise.reject("timed out")
,milliseconds
);
const Fail = function(details){this.details=details;};
const fetchDetails = symbols =>
Promise.all(
symbols.map((s, i) => {
return Promise.race([
fetch(URL + s),
,failIn(2000)//fail in 2 seconds
])
.then(
response=>[response,s],
err = [new fail([err,URL+s]),s]
)
})
)
.then(
responses =>
responses.map(//map to json object from response or Fail type
([response,s]) =>
(response && response.constructor === Fail)
? response
: Object.assign({}, { [s]: response.json() })
)
);
The Fail objects are still included in the result, you could use filter to take them out if you're just going to ignore them:
.then(
responses =>
responses.filter(
([response,s]) => (response && response.constructor !== Fail)
)
.map(
([response,s]) =>
Object.assign({}, { [s]: response.json() })
)
)

Related

How to fetch data on every element in an array using array.map method

I want to fetch data for every object in an array and return an array of new objects with the previous and newly fetched data.I got stucked on getting my result array as my function is returning an array of resolved undefined promises.
I am using a flight search api thats using the apca function for fetching
export const searchApcaLocation = async (dataArr,setDeals) => {
const promises = await dataArr.map(async item => {
apca.request(item.destination);
apca.onSuccess = (data) => {
return fetch('http://localhost:3050/googlePlaceSearch',{
method:"post",
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
cityName:data.airports[0].city
})
})
.then(res => res.json())
.then(imagelinkData => {
const locationObject = {
data: item,
imagelink: imagelinkData.link
}
return locationObject
})
.catch(err => console.log('error on image search',err))
};
apca.onError = (data) => {
console.log('error',data)
};
})
const results = await Promise.all(promises)
return results
}
can someone guide me please on what am I doing wrong?
edit:
as I am trying to fix it realized the problem is I am not returning anything in my map function but if trying to return the apca.onSuccess I am getting an array of functions
just return is missing before fetch function. since you're not returning your promise result it's giving undefined.
export const searchApcaLocation = async (dataArr,setDeals) => {
const promises = await dataArr.map(async item => {
apca.request(item.destination);
apca.onSuccess = (data) => {
return fetch('http://localhost:3050/googlePlaceSearch',{
method:"post",
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
cityName:data.airports[0].city
})
})
.then(res => res.json())
.then(imagelinkData => {
const locationObject = {
data: item,
imagelink: imagelinkData.link
}
return locationObject
})
.catch(err => console.log('error on image search',err))
};
apca.onError = (data) => {
console.log('error',data)
};
})
const results = await Promise.all(promises)
return results
}
The issue in your case might be, that you are using async/await and then blocks together.
Let me sum up what is happening :
1) you await dataArray.map
2) within the map callback, you use the onSuccess method of apca
3) within this method you are using then blocks which won't await until you got a response.
At this point where you return the locationObject, your function already reached the return statement and tries to return results.
But results are of course undefined because they never get resolved at all.
Also, keep in mind that your function returns another promise because you used async/await which you have to resolve where you imported it.
Cheers :)

Trying to figure out how to go around Promise: Pending situation

I could not find a proper way to get my GET handler working. In the code below, when I send a GET request to that endpoint, my array element are all showing as 'Promise: Pending'. Can you suggest a way to go around it? I tried using setTimeout() method but I feel like it is not a proper solution.
Thank you in advance.
apiRouter.get('/photos', (req,res,next) => {
axios.get("https://graph.instagram.com/me/media?fields=id,caption&access_token={some_access_token}")
.then(response => {
const photosArr = response.data.data;
const arr = photosArr.map(id => {
return axios.get(`https://graph.instagram.com/${id.id}?fields=id,media_type,media_url,username,timestamp&access_token={some_acces_token}`)
.then(response => {
return response.data.media_url;
})
})
res.send(arr);
next();
})
})
This would be a useful case to use async / await
The problem is that you are returning the promise in your Array.map(). Even though you have a .then block after your promise, the promise itself is what is being returned because this is running asynchronously.
Something like this should be close
apiRouter.get('/photos', async (req,res,next) => {
const response = await axios.get("https://graph.instagram.com/me/media?fields=id,caption&access_token={some_access_token}")
const photosArr = response.data.data;
const arr = photosArr.map(async (id) => {
const resp await axios.get(`https://graph.instagram.com/${id.id}?fields=id,media_type,media_url,username,timestamp&access_token={some_acces_token}`)
return resp.data.media_url;
};
const final = await Promise.all(arr);
res.send(final);
next();
})
You can use Promise.all() to wait for all promises in an array to resolve:
apiRouter.get('/photos', (req,res,next) => {
axios.get("https://graph.instagram.com/me/media?fields=id,caption&access_token={some_access_token}")
.then(response => {
const photosArr = response.data.data;
Promise.all(photosArr.map(id => {
return axios.get(`https://graph.instagram.com/${id.id}?fields=id,media_type,media_url,username,timestamp&access_token={some_acces_token}`)
.then(response => {
return response.data.media_url;
})
})).then(photos => {
res.send(photos);
next();
})
})
})```

How do I access previous promise response in a .then() chain in axios?

I need access to responseA to get access to a field value further along the chained request. How can that be done elegantly?
axios.get(`/endpoint`)
.then((responseA) => {
// Do something with responseA
return axios.put(signedUrl, file, options);
})
.then((responseB) => {
// Do something with responseA & responseB
})
.catch((err) => {
console.log(err.message);
});
UPDATE: I should probably mention that in my first .then() I return another network request. And it has to happen in sequence.
What is happening inside the first .then() block? In theory, you could return the values you need from responseA to the second .then() block, as whatever you return from the first then block will be available as responseB. In other words, it would look something like this:
axios.get(`/endpoint`)
.then((responseA) => {
// additional request/logic
const moreData = someFunction()
return { responseAdata: responseA.dataYouWant, moreData: moreData }
})
.then((responseB) => {
// now responseA values will be available here as well, e.g.
responseB.responseAdata
// Do something with responseA & responseB
})
.catch((err) => {
console.log(err.message);
});
You have a number of options to do this.
1) Break the chain
let promiseA = axios.get(`/endpoint`)
let promiseB = promiseA.then((responseA) => {
// Do something with responseA
})
return Promise.all([promiseA, promiseB]).then(function([responseA, responseB]) {
// Do what you must
});
2) Use await
let responseA = await axios.get('/endpoint/')
// You can figure out the rest
You can use Promise.all:
axios.get(`/endpoint`)
.then(
responseA =>
Promise.all([
responseA,
axios.get("/endpointB")
])
)
.then(
([responseA,responseB]) => {
console.log(responseA,responseB);
})
.catch((err) => {
console.log(err.message);
});
If anyone is still facing problem then try as below :
axios.get('https://api.openweathermap.org/geo/1.0/direct?q=' + req.body.city + '&limit=1&appid=e43ace140d2d7bd6108f3458e8f5c')
.then(
(response1) => {
let output = response1.data;
axios.get('https://api.openweathermap.org/data/2.5/weather?lat=' + output[0].lat + '&lon=' + output[0].lon + '&appid=e43ace1d1640d2d7bd61058e8f5c')
.then((weatherdd) => {
res.render('index.twig', { weatherdata: weatherdd.data, cityname: req.body.city, todaydatex: todayDate });
})
}
)
.catch(
err => {
res.send(err)
}
);
Hint: As You can see I am using response1 which is returned from the first request and then defined a local variable with output and finally using the data in my next HTTP request (eg: output[0].lat)

Calling multiple APIs without too much nesting

I need to call multiple endpoints with each call dependent on the results of the previous call.
return http.get('url1')
.then(response1 => {
return response1.data
})
.then(data => {
http.get('url2' + data)
.then(response2 => {
return response2.data
}) // etc ... until the 'nth url'
})
It can get quite nested. Is there a way to flatten this, maybe using generators?
Promises are made for flattening:
return http.get('url1').then(response1 => {
return response1.data
}).then(data => {
return http.get('url2' + data);
}).then(response2 => {
return http.get('url3' + response2.data);
}) // ...etc
If your JavaScript engine supports async/await, this can be made shorter and more readable within an async function:
async function demo() {
const response1 = await http.get('url1');
const response2 = await http.get('url2' + response1.data);
const response3 = await http.get('url3' + response2.data);
// ...
return responseN;
}
... and then call that:
demo().then(response => {
console.log(response);
// ...etc
});
I don't know that there's a great solution to avoid the string of then(), but ou don't need to nest:
return http.get('url1')
.then(response1 => response1.data)
.then(data => http.get('url2' + data))
.then(response2 => response2.data )
// etc ... until the 'nth url'
If the pattern is the same in every case, you may be able to pass a list of urls and use reduce()
Flatten promise-chains by returning whenever you have a new promise. However, when you have a non-promise value, don't. It just wastes of a micro-task. Just use the value directly instead:
return http.get('url1')
.then(response => http.get('url2' + response.data))
.then(response => doSomethingWith(response.data))
To get a simple data variable name, use destructuring instead:
return http.get('url1')
.then(({data}) => http.get('url2' + data))
.then(({data}) => doSomethingWith(data))

Issue with async promise

i'm trying to get a list of matching entities from a querystring. i'm using s3 to store my objects.
The problem is that the promise that resolves the 'main promise' is ansych only return the array when given a setTimeOut. Else it returns [undefined, undefined...].
my code looks like this:
getEntities: (type, offset, limit, query) => {
return new Promise((resolve, reject) => {
seekKeys(type, offset, limit, query)
.then((response) => {
let entities = [];
if (response.hasBeenFiltered) { // This is not used yet.
for (let i = offset; i < response.keys.length && i < offset + limit; i++) {
entities.push(matchInstance(instance))
}
resolve(entities)
} else { // here is where my issue's at.
console.log("Keys found: " + response.keys.length)
parseQuery(type, query)
.then((conditions) => {
let matches = response.keys.map((key) => {
readEntity(key).then((instance) => {
logger.debug(instance); // logs instances.
matchInstance(instance, conditions)
.then((isMatch) => {
logger.debug("isMatch", isMatch) // logs true/false ?
if (isMatch) {
entities.push(instance);
}
})
.catch((err) => {
logger.error("Failed to match entity: ", err)
})
})
.catch((err) => {
logger.error("Failed to read entity: ", err)
})
});
/*
Promise.resolve(matches)
.then(() => {
setTimeout(() => {
logger.debug("Match count:", entities.length);
logger.debug("Matching entities:", entities) // logs result of entities
}, 5000)
//resolve(entities)
})
*/
Promise.resolve(matches)
.then(() => {
logger.debug("Match count:", entities.length);
logger.debug("Matching entities:", entities) // logs [undefined, undefined ....]
resolve(entities)
})
})
.catch((err) => {
console.error("Failed to parse query: ", err)
});
})
})
}`
The format is a bit broken. I'm quite sure why.
Please let me know if you need more info.
let matches = response.keys.map((key) => {
// the mapping function:
readEntity(key).then((instance) => {
The mapping function does not appear to return a value. This will create a mapped array with undefined elements.
let matches = response.keys.map((key) => {
return readEntity(key).then((instance) => {
// mapping function
may work better by filling matches with promises to read and process entities. Waiting for the promises to be fulfilled can be accomplished with Promise.all, so
Promise.all(matches).then( ... process the entities array
is more likely to work than
Promise.resolve(matches).then( ... process entities
which won't wait for anything asynchronous unless matches is a pending promise - but if that were so, you would not need to resolve it before calling then.
Note the code indentation is confusing, so check the console for errors to see if matches is in scope when processed - I was unable to see that it was.

Categories

Resources