Wait for a fetch function to finish - javascript

I'm trying to use the curseforge API in a project using fetch in nodejs18, this is the code I'm using:
ids = ["238222","60089","556448"]
const headers = {
'Accept':'application/json',
'x-api-key':'API KEY'
};
function getMods(id){
fetch("https://api.curseforge.com" + '/v1/mods/' + id,
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body.data.name);
});
}
ids.forEach(element => {
getMods(element)
});
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
console.log("download finished")
With that code what you want to be printed in the terminal is:
Alex's Delight
Mouse Tweaks
Just Enough Items (JEI)
download finished
but when running the program I get this in the terminal:
download finished
Alex's Delight
Mouse Tweaks
Just Enough Items (JEI)
This happens because the fetch function is asynchronous, I have tried all the methods to solve this problem but no solution is what I want.
What I want is for the program to wait for the foreach and fetch to finish to continue executing.

Here's roughly how it's done:
const ids = ["238222","60089","556448"]
const headers = {
'Accept':'application/json',
'x-api-key':'API KEY'
};
async function getMods(id){
const res = await fetch("https://api.curseforge.com" + '/v1/mods/' + id, {
method: 'GET',
headers,
})
const body = await res.json();
console.log(body.data.name);
}
async function run() {
for(const element of ids) {
await getMods(element);
}
console.log("download finished")
}
run();
If you want to download the mods in parallel instead of one at a time, this is how the run() function should look like instead:
async function run() {
await Promise.all(
ids.map(element => getMods(element))
);
console.log("download finished")
}
If you use ESM, you can avoid the 'run' function and just use await at the top-level. To easily take advantage of that with node, just save your file with the .mjs extension.
Then the final code might look like this:
const ids = ["238222","60089","556448"]
const headers = {
'Accept':'application/json',
'x-api-key':'API KEY'
};
async function getMods(id){
const res = await fetch("https://api.curseforge.com" + '/v1/mods/' + id, {
method: 'GET',
headers,
})
const body = await res.json();
console.log(body.data.name);
}
await Promise.all(
ids.map(element => getMods(element))
);
console.log("download finished")

Something like this can work:
...
promisesArray = []
ids.forEach(element => {
promisesArray.push(getMods(element))
});
Promise.all(promisesArray).then(() => {
console.log("download finished");
});

Since you run nodejs 18 you can use top level await
const ids = ["238222", "60089", "556448"]
const headers = {
'Accept': 'application/json',
'x-api-key': 'API KEY'
};
async function getMods(id) {
await fetch("https://api.curseforge.com" + '/v1/mods/' + id, {
method: 'GET',
headers: headers
})
.then(res => res.json())
.then(body => {
console.log(body.data.name);
});
}
try {
await Promise.all(ids.map(id => getMods(id)))
console.log("download finished")
} catch {
// Handle error
console.error("download error")
}

Related

React Native get return value of async function

I am trying to get pub locations data from MYSQL server and my fetch function works well. But after that, this try-catch block does not return anything. I also tried without try-catch block but it does not change anything
getPubsFromDatabase = async () => {
let response = await fetch(fetchDataUrl, {
method: 'POST',
headers:
{
'Accept': 'application/json',
'Content-Type': 'application/json',
},
});
try{
let json = await response.json();
console.log(json)
return json;
}
catch (error) {
console.log(error);
}
}
And here, I am trying to get the return value of the function. But in this version, I cannot even see any console.log output. What I mean by the version is, if I put 2nd line out of the async block without "await" keyword, I can see the console.log but I it gives "undefined" then.
(async () => {
const locationsData = await getPubsFromDatabase();
console.log(locationsData)
})()
You can use then and catch in the function fetch.
const getPubsFromDatabase = () => {
return fetch(fetchDataUrl, {
method: 'POST',
headers:
{
'Accept': 'application/json',
'Content-Type': 'application/json',
},
}).then(async (response) => {
const json = await response.json().then((data)=>{
return data;
}).catch((err)=>{
console.log('error from json method: ',err);
return { error: {
message: 'error from json method',
object: err
}};
});
console.log(json);
return json;
}).catch((error) => {
console.log('error from request: ', error);
return {
error: {
message: 'error from request', object: error
}
};
});
}
And when you use the method getPubsFromDatabase would be of the next way:
(async () => {
const locationsData = await getPubsFromDatabase();
console.log(locationsData);
})()
You can use Promise to return either result or error, In the example given below i have used axios library but you can also try same with fetch api
For Example
export const getData = () => {
return new Promise((resolve, reject) => {
const url = ApiConstant.BASE_URL + ApiConstant.ENDPOINT;
const API_HEADER = {
"Authorization": token,
};
axios
.get(url, {
headers: API_HEADER,
})
.then((res) => {
resolve(res.data);
})
.catch((error) => {
// handle error
reject(error);
console.log(error);
});
})
}
You can call the above function in component or screen like this:
getData().then(
(data) => {
}
).catch(
error => {
console.log(error)
}
)
I solved the problem by instead of returning the value from the function, I set the value inside the function. This seems to work now.
const [locationsData, setLocationsData] = useState();
getPubsFromDatabase = async () => {
let response = await fetch(fetchDataUrl, {
method: 'POST',
headers:
{
'Accept': 'application/json',
'Content-Type': 'application/json',
},
});
try{
let json = await response.json();
setLocationsData(json); // Here, I changed the value of the locationsData instead of returning
}
catch (error) {
console.log(error);
}
}
And to call the function:
useEffect(()=> {
getPubsFromDatabase();
},[])

How to return data from Promises if second fetch request depends on first?

I want to get some data from 2 apis, but i need some properties from the first api to make the fetch request to the second api. I also need the entire response from the first api.
export const fetchTrip = createAsyncThunk(
'trip/fetchTrip',
async (payload, thunkAPI) => {
const result = fetch(
'http://localhost:5000/user/trip/' + payload, {
mode: 'cors',
credentials: 'include',
method: "get",
headers: {
'Content-Type': 'application/json'
},
})
.then(response => response.json())
.then(data => {
const places= data.trips[0].places
return Promise.all(places.map(place=>
fetch('http://localhost:5000/api/tripadvisor/' + place.id)
))
.then(resp => resp.json())
})
console.log(result)
return result
}
)
So the first fetch request will respond with some data such as
{
title: "Cali Trip",
budget: "1000",
places: [
{id: "123"},
{id: "234"},
{id: "345"}
]
}
I am using the place's id to fetch more details about that place. in the end, i want to return the trip detail from the first api, and all the details of the multiple places from the second api. I've tried the above code, but my response is undefined. ty in advance for any help!!
This would be a lot cleaner if you didn't mix async / await with .then().
What you can do is return the response from the first request with the places array re-assigned with the values resolved from the secondary requests.
export const fetchTrip = createAsyncThunk(
"trip/fetchTrip",
async (payload, thunkAPI) => {
const res = await fetch(
`http://localhost:5000/user/trip/${encodeURIComponent(payload)}`,
{
credentials: "include", // cookies? Do you even need this?
}
);
if (!res.ok) { // don't forget to check for failures
throw res;
}
// pull out the first "trip" from the "trips" array
const {
trips: [{ places, ...trip }],
} = await res.json();
// respond with the "trip"
return {
...trip,
// resolve "places" by mapping the array to a new promise
// and awaiting its result
places: await Promise.all(
places.map(async ({ id }) => {
const placeRes = await fetch(
`http://localhost:5000/api/tripadvisor/${encodeURIComponent(id)}`
);
// again, check for errors
if (!placeRes.ok) {
throw placeRes;
}
// resolve with the place data
return placeRes.json();
})
),
};
}
);
Since you are already making the function async, you could use await to make it more easy to follow, something along these lines, just a sketch for you to elaborate:
export const fetchTrip = createAsyncThunk(
'trip/fetchTrip',
async (payload, thunkAPI) => {
const data = await fetch('http://localhost:5000/user/trip/' + payload, {
mode: 'cors',
credentials: 'include',
method: 'get',
headers: {
'Content-Type': 'application/json',
},
}).then((resp) => resp.json());
// here your first result
console.log(data);
const places = data.trips[0].places;
const result = await Promise.all(
places.map((place) =>
fetch('http://localhost:5000/api/tripadvisor/' + place.id).then(
(resp) => resp.json()
)
)
);
// here the second result
console.log(result);
// overwrite places with second result
return {
...data,
places: result,
};
}
);
It seems you are mixing async/await code with Promise chaining code. While that's certainly valid, if we clean up your code a bit you can see that this is trivially done if you convert all of your code to async/await:
const response1 = await fetch('...');
const data1 = await response.json();
const data2 = await Promise.all([...]);
Of course, this can be done with Promise chaining by saving the data from inner calls into a variable outside the calls, but that's a far inferior option:
let data1;
let data2;
fetch('...')
.then((response) => response.json())
.then((data) => {
data1 = data;
return Promise.all([...]);
})

Get http response status code after response.json()

I would like to get http status code after response.json to use it in my logic later, can I do something with it?
function apiRequest(path, options) {
fetch("api/" + path, options)
.then(response => response.json())
.then(data => {
let res = {
code: 200 //I want to put http status code here,
data: data
}
return res;
})
}
This is slightly tricky using then (as you are currently doing) because you want to get data directly from the response (which is a promise) and some more data from the parsed body (which is another promise).
So you can wrap the status and the data promise in a Promise.all and return that from the first then:
const apiRequest = () => {
const url = "//swapi.dev/api/planets/1/";
fetch(url)
.then((response) => Promise.all([response.status, response.json()]))
.then(([status, data]) => console.log({status, data}))
}
… but it would be easier to use async/await syntax and ditch the callbacks and you then only have to worry about a single function (and therefore scope) rather than multiple.
const apiRequest = async () => {
const url = "//swapi.dev/api/planets/1/";
const response = await fetch(url);
const data = await response.json();
const status = response.status;
console.log({status, data})
}
As an alternative you could consider async/await. That way you have access to response and data at the same time more easily.
async function apiRequest(path, options) {
const response = await fetch("api/" + path, options)
const data = await response.json()
let res = {
code: response.status,
data: data
}
// Do something with res
}
Try this
function apiRequest(path, options) {
fetch("api/" + path, options)
.then(response => Promise.all([Promise.resolve(response.status), response.json()]))
.then(([status, data]) => {
let res = {
code: status //I want to put http status code here,
data: data
}
return res;
})
}
you can git it in the first then before you return response.json something like this
function apiRequest(path, options) {
fetch("api/")
.then((response) => {
let status = response.status;
console.log("status", status);
return response.json();
})
.then((data) => {
console.log(data);
});
}
apiRequest();

React.js - const in async becomes undefined

const test = order.restaurantId;
console.log(test); //here test == 3
const getAvailableHours = async () =>{
console.log(test); //test == undefined
await fetch(`API Address`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
})
.then(response => {
return response.json();
})
.then(responseText => {
console.log(responseText);
})
.catch(error => {
console.log(error);
});
}
Hi, I am trying to fetch data from API by using restaurant ID but when I'm passing the ID to async it becomes undefined.
I am new in React.js, anyone has any ideas?
async await syntax allows you to avoid .then method, so you can handle promises with a more readable syntax. With try catch blocks you can handle errors, try this:
const getAvailableHours = async () => {
try {
let response = await fetch('https://api-address-goes-here/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
let responseText = await response.json();
return responseText;
} catch (err) {
console.log(err)
}
}

Iterating through multiple pages of an API response in a JS promise function

I have the following promise function which uses fetch to get data from an API:
const getContacts = token =>
new Promise((resolve, reject) => {
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then((data) => {
resolve(data);
})
.catch(err => reject(err));
});
This function is then called in a different file:
getContacts(token)
.then((data) => {
const contacts = data.data;
console.log(contacts);
})
.catch(err => console.error(err));
When there is a larger amount of data returned from the API, it is paginated. The response includes a link that needs to be fetched in order to get the next page. I want my code to first iterate through all pages and collect all data, then resolve the promise. When execution reaches the const contacts = data.data line, it should have data from every page (currently it returns only the first page).
What would be the best way to achieve this?
EDIT:
I tried recursion inside the getContacts function. This way I can iterate through all pages and get all data in one object, but I don't know what's the right way to resolve this back to the code, which initially called the function. The code below doesn't resolve correctly.
const getContacts = (token, allData, startFrom) =>
new Promise((resolve, reject) => {
if (startFrom) {
url = `${url}?${startFrom}`; // the api returns a set of results starting at startFrom (this is an id)
}
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then((data) => {
let nextPageExists = false;
Object.assign(allData, data.data);
data.links.forEach((link) => {
if (link.rel === 'next') {
nextPageExists = true;
getContacts(token, allData, link.uri);
}
});
if (!nextPageExists) {
resolve({ data: allData });
}
})
.catch(err => reject(err));
});
First of all, do not use the new Promise constructor when fetch already returns a promise.
Then, just use a recursive approach and chain your promises with then:
function getContacts(token, allData, startFrom) {
return fetch(startFrom ? url + '?' + startFrom : url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}).then(response => response.json()).then(data => {
Object.assign(allData, data.data);
const nextPage = data.links.find(link => link.rel === 'next');
if (!nextPage)
return allData;
else
return getContacts(token, allData, nextPage.uri);
});
}
Here's a generic function using async/await syntax.
It returns itself until currentPage equals totalPages. You can retrieve these keys from your API response.
async function getData(perPage, page, options, allData = []) {
// fetch data
let base = 'https://api.example.com';
let url = `${base}?perPage=${perPage}&page=${page}`;
let response = await fetch(url, options);
let data = await response.json();
// push this data object (or data.data... whatever) into allData array
allData.push(data);
// get 'totalPages' and 'currentPage' (or whatever your API names these)
let totalPages = data.pagination.total_pages;
let currentPage = data.pagination.current_page;
if (currentPage == totalPages) {
// you're done
return allData;
} else {
// get the next page and repeat
page++;
return getData(perPage, page, options, allData);
}
}
Calling it:
const options = {
method: 'GET',
headers: {
Accept: 'application/json',
appId: 'APP_ID',
apiKey: 'APP_KEY',
'Content-Type': 'application/json'
}
};
let perPage = 100;
let page = 1;
getData(perPage, page, options).then((data) => {
console.log(data)
}).catch((error) => {
console.log(error)
})

Categories