How to wait for JavaScript function to complete in react? [duplicate] - javascript

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I am trying to fetch data from an endpoint in react but don't know exactly how can I wait for the called function to return and then pass the cursor to next line.
FetchData.js
// ...
componentDidMount () {
const url = 'http...';
const options = {
headers: {'Content-Type': 'application/json'},
method: 'GET'
}
const data = fetchDataFromUrl(url, options);
this.setState({ item: data },
() => this.updateItem())
}
console.log(this.state.item)
// ...
fetchData.js
export const fetchDataFromUrl = (url, options) => {
const repsonse = fetch(url, options)
.then(res => res.json())
.then(data => {
return data
})
}
This does return the data but in console.log() I get undefined, after sometime (ie in some other function) I am able to see the data contents. How can I wait for the fetchDataFromUrl function to fetch the data before the state gets updated. I used
const data = async() => fetchDataFromUrl(url, options); but it does not help.

You can simply use, async and await for your requirement
componentDidMount () {
this.getData()
}
getData= async()=>{
const url = 'http...';
const options = {
headers: {'Content-Type': 'application/json'},
method: 'GET'
}
const data = await fetchDataFromUrl(url, options);
this.setState({ item: data },
() => this.updateItem())
}
user fetch function should also be async function.
export const fetchDataFromUrl = async(url, options) => {
const repsonse = await fetch(url, options);
return repsonse; //Modify you response
}

Related

fetch request returning promise [duplicate]

This question already has answers here:
How to return many Promises and wait for them all before doing other stuff
(6 answers)
Closed 4 months ago.
i created a function named getCartItems which calls getSingleItems passing id as argument. if i log the json result in getSingleItem it is showing object of the product correctly but when i try to access the function call value i get a promise how to resolve it?
const getSingleItem = async (id)=>{
const response = await fetch("http://localhost:4000/products/"+id, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
})
const json = await response.json();
return json;
}
const getCartItems = () => {
let currArr = JSON.parse(localStorage.getItem('cart'));
let newArr = currArr.map( async (el)=>await getSingleItem(el.id))
console.log(newArr);
setCartItems(newArr);
}
useEffect(()=>{
getCartItems();
}, [])
if try to use for loop instead of map it shows promise pending and throws connection error.
You need to resolve the promises from the map method with Promise.all and await for it before setting the cartItems state.
const getSingleItem = async (id)=>{
const response = await fetch("http://localhost:4000/products/"+id, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
})
const json = await response.json();
return json;
}
const getCartItems = async () => {
let currArr = JSON.parse(localStorage.getItem('cart'));
let newArr = await Promise.all(currArr.map( async (el)=>await getSingleItem(el.id)))
console.log(newArr);
setCartItems(newArr);
}
useEffect(()=>{
getCartItems();
}, [])

How to collect result from promise call in Javascript? [duplicate]

This question already has answers here:
How to return value from an asynchronous callback function? [duplicate]
(3 answers)
Closed 6 months ago.
function foo() {
let url = `https://foobar.com`
fetch(url, {
method: 'POST',
headers: headers
}).then(response => {
return response.json();
}).then(data => {
return data['jobs'];
}).then(jobs => {
let successful = [];
jobs.forEach(job => {
let jobUrl = job['url'];
fetch(jobUrl, {
headers: headers
}).then(response => {
return response.json();
}).then(data => {
let job_status = data['status'];
let job_timeStamp = data['timestamp'];
if (job_status === 'SUCCESS') {
console.log(job_status, job_timeStamp);
successful.push({jobUrl:jobUrl, timeStamp:job_timeStamp});
}
})
});
console.log(successful);
})
}
foo()
When I am running the above code, successful array is not getting populated. How can I populate the successful array? I should be able to sort that array later based on timestamp.
The issue with your code is that fetch is asynchronous, so none of the fetches will have run yet when you console.log(successful)
Here's two alternatives, using async / await since that will simplify the code immensely - I've also used a liberal amount of destructuring sugar
If all the jobUrl fetches can be done in parallel - use this
async function foo() {
// function to get one jobUrl data
// called by map later on
const getJob = async ({url:jobUrl}) => {
const response = await fetch(jobUrl, {headers});
const {status, timestamp: timeStamp} = await response.json();
if (status === 'SUCCESS') {
return {jobUrl, timeStamp};
}
};
let url = `https://foobar.com`;
const response = await fetch(url, {method: 'POST', headers});
const {jobs} = await response.json();
const unfiltered = await Promise.all(jobs.map(getJob));
// this "unfiltered" array will have `undefined` items for when status !== SUCCESS
// so filter them out
const successful = unfiltered.filter(v => v);
// here you have successful populated and can use it
}
If the jobUrl fetches have to be done one by one - use this
async function foo() {
let url = `https://foobar.com`;
const response = await fetch(url, {method: 'POST', headers});
const {jobs} = await response.json();
const successful = [];
for ({url:jobUrl} of jobs) {
const response = await fetch(jobUrl, {headers});
const {status, timestamp: timeStamp} = await response.json();
if (status === 'SUCCESS') {
successful.push({jobUrl, timeStamp});
}
};
// here you have successful populated and can use it
}

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([...]);
})

Promise { <pending> } - for last async function

I have two main functions. The first one gets the SOAP data from an API. The second one parses it to json and then formats it into the object I need to store. I have been logging and seeing the return values, and as of so far until now, it's been fine. However, I am calling exports.createReturnLabelfrom my test file and all I can see is Promise { <pending> } . I am returning a value at the last function. Does this code look weird to your? How can I clean it up?
const soapRequest = require('easy-soap-request');
const xmlParser = require('xml2json')
exports.createReturnLabel = async () => {
const xml = hidden
const url = 'https://ws.paketomat.at/service-1.0.4.php';
const sampleHeaders = {
'Content-Type': 'text/xml;charset=UTF-8',
};
const auth = async () => {
const {
response
} = await soapRequest({
url: url,
headers: sampleHeaders,
xml: xml,
timeout: 2000
});
const {
body,
statusCode
} = response;
return body
}
const fetchLabel = async () => {
const soapData = await auth();
try {
const json = xmlParser.toJson(soapData)
const labelData = JSON.parse(json)["SOAP-ENV:Envelope"]["SOAP-ENV:Body"]["ns1:getLabelResponse"]
return {
courier: 'dpd',
tracking_number: labelData["return"]["paknr"],
barCode: labelData["return"]["barcodecontent"],
printLabel: labelData["return"]["label"],
_ref: null
}
} catch (e) {
return (e)
}
}
return fetchLabel()
}
calling from my test file return console.log(file.createReturnLabel())
There's an async function call inside your code.
Should be: return await fetchLabel(), so that it awaits for completion before going on its merry way.

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