Basically I want to call an API twice, make an array [res1, res2] of the two responses, and then operate on this array. My code goes something like that:
function f() {
apiCall1(params1)
.then(response1 => [response1, apiCall2(params2)])
.then(data => someFunction(data))
}
Unfortunately, this method does not work. I get undefined properties of data[0] and data[1]. However, if I make only one API call, everything works fine. I am wondering whether my syntax is wrong and also what would be a good way to implement this? Thanks.
You can group promises with Promise.all, for example:
function f() {
Promise.all([apiCall1(params1), apiCall2(params2)])
.then(data => {
const response1 = data[0];
const response2 = data[1];
})
}
cf. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Related
I wrote a function which when I use the return keyword, I only get the first item of the list returned instead of all items. The code works properly without the return keyword.
code snippet
function generateNames(){
for (let i = 0; i < 5; i++) {
const playersName = fetch('https://www.balldontlie.io/api/v1/players?per_page=5')
.then(response => response.json())
.then(json => console.log(json['data'][i]['first_name']))
// return playersName
}
}
generateNames()
The result i get is;
Ike
Ron
Jabari
MarShon
Lorenzo
but when I uncomment the, return playersName line of code, I only get the first value returned.
The result i get is;
Ike
After refactoring the code:
async function generateNames() {
var playerList = [];
const playersName = await fetch('https://www.balldontlie.io/api/v1/players?per_page=5')
.then(response => response.json())
.then(json => {
for (let i = 0; i < 5; i++)
playerList.push(json['data'][i]['first_name'])
})
// return playersName
return playerList;
}
(async () => console.log(await generateNames()))()
I think this is what you want:
async function fetchPlayerNames( count = 1 ) {
let playersResponse = await fetch(`https://www.balldontlie.io/api/v1/players?per_page=${count}`)
let players = await playersResponse.json()
let desiredPlayers = players.data.slice(0, count)
return desiredPlayers.map( player => player.first_name )
}
(async function main() {
let names = await fetchPlayerNames(5)
console.log(names)
})()
Some notes:
async/await is easier to work with than chaining .then handlers, so if your function can be marked async (and some cannot), you should have a strong preference to avoid any .then or .catch inside that function. Your refactor makes the function async, but continues to use .then.
Your refactor won't work anyway, because return playerList will execute before either of the .then functions executes. As a result, I'd expect your refactor to return an empty array immediately every time.
Even though your refactor immediately returns an empty dataset, it still makes the API call five times. That is silly. The URL suggests that the API response can include multiple players, and so your plan should be: (1) make a single request that contains all the rows you want, and then (2) extract the items from that list that you want. If you are careless about making extra network calls, the people who own the API may ultimately block your application because it wastes resources.
Avoid hard-coding things like the 5. That is what is known as a "magic number". Instead, your function should be provided with the desired number. Calling code will get the correct number from a settings file or user input or something else. My version takes it as an argument, but hard-codes it at the call site because there's not really a better place in such a small code sample.
I am going to criticize your naming. Naming matters. Your function does not "generate" names -- it does not create or invent names randomly, but that is what the English word "generate" implies. Your function downloads names from the internet (the correct verb for which is "fetch"). If you name your functions badly, your app will become a mess.
I have this code, wherein i'd like to make it in single array.
The output that data produce, is like this:
connections.elements.map((val: any) => {
const url = 'link'
return new Promise((resolve) => {
axios.post(url, val.firstName).then((res: { data: any }) => {
resolve(searchRequestBuilder(res.data.AllResults));
});
});
});
const searchRequestBuilder = async (data: any) => {
console.log(await data);
// for await (let resolvedPromise of data) {
// console.log(resolvedPromise);
// }
};
What I'd like to do is like this:
I already tried to make a variable and use .push, but it still doesn't combine in a single array. What was the thing I am missing?
So since your .map function will return an array of promises, you can store that in a variable, and you can use Promise.all or Promise.allSettled to wait for all the apis to resolve and you can get all the results in a single array in the order that it is requested.
Not part of the question, another thing is if you are not careful, and use await on each api request, it can make a waterfall pattern of requests, which means it will wait for API 1 to finish, then API 2 to finish.
But the way its written here, it will make all the requests parallely, so this will request all the APIs together, and once the last one gets resolved the Promise.all or allSettled will trigger its callback.
const searchRequestBuilder = async (data: any) => {
console.log(await data);
}
async function makeAPIrequests(){
let arrrayOfPromises = connections.elements.map((val: any) => {
const url = 'link'
return new Promise((resolve) => {
axios.post(url, val.firstName).then((res: { data: any }) => {
resolve(searchRequestBuilder(res.data.AllResults));
});
});
});
Promise.allSettled(arrrayOfPromises).
then((results) => console.log(results))
}
Edit:
Also in addition to this, I do not think that you need to return a new Promise inside the map funciton, if you just return axios it should work, since axios itself will return a promise.
The main problem there could be you are making a new call per each item of your elements array. This may create performance issues. The best practice may be to do only one async call and then get all items.
Anyway, if for some reason you really need to make all these ajax calls. Think you are working with asynchronous code, then you really don't know when all calls are answered. To fix this, you need to call your searchRequestBuilder after had made all calls.
I suggest you use Promise.all method.
I left you this link to show you how it works. Basically what you need to do is save all axios.post promises in an array and after loop your elements array call Promises.all passing the array with your promises, then you will be able to execute your searchRequestBuilder without problems.
You can try using .concat:
let allData = [];
allData = allData.concat(await data);
I would like to get data from two API. in the firest call (getDatafromGeonames) I want to get latitude and longitude then I want to pass them as parameters to the second call (getWaetherData) to get weather information then I want to store the result and pass to my endpoint
Here is my try:
const res1 = getDatafromGeonames(geonameAPIURL,city,geonamesKey).then(res =>{
getWaetherData(res.address.lat, res.address.lng)})
Promise.all([res1]).then((res)=>{
//do somthing
console.log(res);
})
a screenshot of the result:
I don't know why it is jumping to then and printing Undefined before it is executing getWaetherData? How can I get data from the first API then use these data to get weather info then do something with these data
thanks a lot :)
Promise.all(promises[]) method is meant to execute some logic when all promises[] has been executed. This would be helpful if you have 2 independant async calls and you want to do something after both of them has been executed.
By what you have described, seems that the second API call you have to do needs the result of the first API call, so the promise operator you should use is .then().
const waetherData = getDatafromGeonames(geonameAPIURL,city,geonamesKey)
.then(res => getWaetherData(res.address.lat, res.address.lng))
or, in EMS6 style:
const getWaetherData = async (geonameAPIURL, city, geonamesKey) => {
const geodata = await getDatafromGeonames(geonameAPIURL,city,geonamesKey);
return await getWaetherData(geodata.address.lat, geodata.address.lng)
}
I have put this last code into a method because if you want to use await operator you need to be in a async context.
Promise.all only works for Promises you can run in parallel, you have to run them in sequence instead:
getDataFromGeonames(geonameAPIURL, city, geonamesKey)
.then((res) => getWeatherData(res.address.lat, res.address.lng))
.then((res) => {
// do something
console.log(res);
return res;
});
Thanks for taking the time to look at this. I ran into some behavior that was unexpected and that I cannot figure out while working in an aws lambda.
I'm executing a fetch, then doing some different things based on whether it succeeded or failed, and eventually wanting to use the fetch.json() method if everything is kosher. I'm using await to make the interior of my lambda handler "synchronous."
Everything was fine when I assigned the result of the fetch to a variable and then inspected it. But I like tidy code, so I decided to use destructuring assignment, and suddenly things started breaking.
To demonstrate what I'm confused about, I've included a code snippet that duplicates the issue and shows five different things - the first two work, the rest break.
In the third example, "r" is an empty object. The rest operator breaks it, which I do not understand. Is it making a new object and refusing to copy properties over? Do the properties not exist at the time that the destructuring happens? If so, why would ok and status destructure properly?
Four is just a simplified example of three's problem.
Five is also confusing me, because I was able to get the json method, but then it throws an illegal invocation exception when I try to execute it.
Snippet included below:
const one = async () => {
const r = await fetch('https://reqres.in/api/users?page=2');
console.log(await r.json());
}
const two = async () => {
const r = await fetch('https://reqres.in/api/users?page=2');
const { ok } = r;
console.log(await r.json());
console.log(ok);
}
const three = async () => {
const { ok, status, ...r } = await fetch('https://reqres.in/api/users?page=2');
try {
console.log(await r.json());
} catch (e) {
console.log(e);
console.log(ok);
console.log(status);
console.log(r);
}
}
const four = async () => {
const { ...r } = await fetch('https://reqres.in/api/users?page=2');
console.log(r);
}
const five = async () => {
const { json } = await fetch('https://reqres.in/api/users?page=2');
try {
console.log(await json());
} catch (e) {
console.log(e);
console.log(json);
}
}
[one, two, three, four, five].forEach(f => f());
Thanks again for your time.
Note: For some reason SO is logging the exceptions as empty objects, so here's a fiddle: https://jsfiddle.net/ygbm6kwq/4/
Edit: Accidentally left out a line in example two, snippet and fiddle updated.
The rest operator breaks it, which I do not understand. Is it making a new object and refusing to copy properties over?
Yes, exactly that. The property rest element creates a new object and copies any left-over enumerable own properties into it. But the new r will be a plain object, not a Response instance, and as such it does not inherit a .json() method.
I was able to get the json method, but then it throws an illegal invocation exception when I try to execute it.
Yes, it's a method. And as such it must be invoked on the instance, with a proper this value. You can technically do
const response = await fetch('https://reqres.in/api/users?page=2');
const { json } = response;
console.log(await json.call(response));
or even without destructuring, Response.prototype.json.call(response), but there's no point in that.
I have a method like this:
doSomeRequests(listOfRequestParameters) {
let requests = listOfRequestParameters.map(parmeter => {
return axios.get(url + parmeter)
.then(data => {
const parameters= data.parameters;
return axios.delete(url, parameters)
})
})
return Promise.all(requests);
}
In this function I want to complete API requests for each element in a list (listOfRequestParameters), but for every element in the list I have to do two API requests.
The axios methods both return Promises.
The problem is that I do catch the result of the Promise returned y doSomeRequests method, but if one API requests fail I get an UnhandledPromiseRejectionWarning.
What am I missing? Is there a way to do this kind of Promise chain?
Thanks to #charlietfl for asking me if I really do doSomeRequests(..).then().catch(). Turns out I don't, I accidentally did doSomeRequests(..).then().then() but was somehow convinced the I need to search for the error in the "complex" Promise chain.
Anyway, I am sorry for the useless question, thank you for your help.
You need to make sure the map call returns a list of promises, simplifying with async/await you'll get something along the lines:
async doSomeRequests(listOfRequestParameters) {
return Promise.all(listOfRequestParameters.map(async parmeter => {
const data = await axios.get(url + parameter);
const parameters = data.parameters;
return axios.delete(url, parameters);
}));
}
await doSomeRequests(listOfRequestParameters);