Calling multiple APIs without too much nesting - javascript

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))

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();
})
})
})```

Promise.all returns [[PromiseValue]] but I need array of all returned values

I am trying to return array of json objects, but below code returns [[PromiseValue]] and data surely is there.
"results from function"
How can I get array of json objects and set it to state
NOTE: if I call .then(data => console.log(data)) inside getEndResults function that works but then I can't setState in there as it throws an error
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
getUserCredits = async (userId, creditId) => {
return await fetch(
`${someLink}/userId/${userId}/credit/${creditId}`,
{
method: "GET",
}
)
}
getEndResult = async () => {
const userId = await this.getUserId(userId);
const userData = await this.getUserData(userId);
const checkData = await Promise.all(userData.checks.map(check => {
return check.checkIds.map((checkIds) => {
return this.getUserCredits(userId ,checkIds)
.then(res => res.json())
.then(data => console.log(data))
// Can't setState here
// .then(data => this.setState({ results: data }))
})
}))
console.log(checkData);
}
You're creating a 2-dimensional array of promises, and then passing that into Promise.all. Promise.all only knows how to work with single-dimensional arrays, so what it sees is an array with things that aren't promises. For non-promises, promises.all just immediately resolves to the value it was given.
You will need to flatten out your 2-d array before sending it to Promise.all. If you have a polyfill for array.prototype.flatmap (which will soon be added to javascript, but isn't there yet), this can be done like:
const checkData = await Promise.all(userData.checks.flatMap(check => {
return check.checkIds.map((checkIds) => {
return this.getUserCredits(userId, checkIds)
.then(res => res.json());
});
If that function is not available to you, then you could write your own function for flattening a 2d array, something like this:
function flatten(arr) {
const result = [];
arr.forEach(val => {
if (Array.isArray(val)) {
result.push(...val);
} else {
result.push(val);
}
});
return result;
}
// used like:
const promises = flatten(userData.checks.map(check => {
// ... etc
}));
const checkData = await Promise.all(promises);

Why can't I store the result of a promise (or a chained promise) in a variable?

This is a silly question but can you explain what is wrong with this code?
Why can't I perform this?
const fetch = require('node-fetch');
const fetchProm = (() => {
return fetch('https://api.github.com/users/github');
}).then((response) => {
return response.json();
}).then((json) => {
console.log(json)
});
Declaring a function is not the same as calling one
You are not calling the function that returns the promise, just declaring it. You'll need to add an additional set of parentheses before the first .then() in order to actually call the function:
const fetch = require('node-fetch');
const fetchProm = (() => {
return fetch('https://api.github.com/users/github');
})().then((response) => {
return response.json();
}).then((json) => {
console.log(json)
});
If you want to call everything at a later time, you need to place the whole thing in its own function where the promises get handled in an isolated scope:
const fetch = require('node-fetch');
const fetchProm = () => {
fetch('https://api.github.com/users/github')
.then(response => response.json())
.then(json => console.log(json));
};
fetchProm();

Asynchronous filtering an array of Promises

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() })
)
)

Categories

Resources