I'm using a chain of then statements to transform data after a fetch request. The code is within a React app, but I think this is just a Javascript question.
Here's my current code:
componentDidMount(){
fetch(this.state.dataRouteDirections)
.then(data => data=data.json())
.then(data => console.log(data))
.then(data => data.unshift(this.state.emptyDirections))
.then(data => this.setState({directions:data}))
.then(()=>{
var makes = this.state.directions.map(directions=>directions.acf.make); //get all make categories
function removeDuplicates(arr){
let unique_array = arr.filter(function(elem, index, self) {
return index===self.indexOf(elem);
});
return unique_array
}
makes = removeDuplicates(makes);
this.setState({makes:makes});
})
}
The line giving me trouble is ".then(data => data.unshift(this.state.emptyDirections))". It is telling me that the variable data is undefined in this statement. Indeed, if I replace this line with the same line as above, console logging data twice, the first console log logs correctly and the second is undefined.
What am I not understanding about fetch/then/JS? I'm coming from a PHP background and async stuff is still a challenge.
Question is answered below, here's my updated code:
componentDidMount(){
fetch(this.state.dataRouteDirections)
.then(data => data=data.json())
.then(data => {data.unshift(this.state.emptyDirections); return data;})
.then(data => this.setState({directions:data}))
.then(()=>{
var makes = this.state.directions.map(directions=>directions.acf.make); //get all make categories
function removeDuplicates(arr){
let unique_array = arr.filter(function(elem, index, self) {
return index===self.indexOf(elem);
});
return unique_array
}
makes = removeDuplicates(makes);
this.setState({makes:makes});
})
}
The reason you are having this is because console.log(data) returns undefined. You should write smth like:
.then(data => {console.log(data); return data})
Related
I have got two data tables I want to query some data from, so the way i thought about doing it is fetch url and based on a field from list, I do fetch(url) once more to get related data from another data table and I want to append them together to display on the browser. The issue here is that for loop iteration doesn't wait until the previous fetch is finished. I am using django_restframework, and it is me trying to fetch data by passing criteria(variables) through urls. can anyone help?
var url = 'http://127.0.0.1:8000/creditor-detail/'+pmt_id+'/supplier/'+crm_spplier_id+'/'
var results = fetch(url)
.then((resp) => resp.json())
.then(function(item){
var list = item
for (var i in list){
getCustomer(list[i].customer_id)
.then(function test(user) {
return user[0].customer_name
});
var spmt = `
<tr id="data-row-${i}">
<td>${list[i].po_no}</td>
<td>${list[i].amount}</td>
<td>${I want return value[user[0].customer_name]}</td>
</tr>
`
wrapper.innerHTML+= spmt
}
})
function getCustomer(customer_id){
var url = 'http://127.0.0.1:8000/user-detail/'+customer_id+'/'
var results = fetch(url)
.then((resp) => resp.json())
.then(function(item){
return item
})
return results
}
I have changed to:
function test() {
const url = 'http://127.0.0.1:8000/creditor-detail/'+pmt_id+'/supplier/'+crm_supplier_id+'/'
let promises = [];
const results = fetch(url)
.then(resp => resp.json())
.then(function (item) {
var list = item;
for (var i in list) {
promises.push(getCusotmer(list[i].customer_id));
console.log(list[i].customer_id)
}
})
Promise.all(promises)
.then((results) => {
console.log("All done", results);
})
.catch((e) => {
console.log(err)
});
}
function getCusotmer(customer_id) {
return new Promise((resolve, reject) => {
const url = 'http://127.0.0.1:8000/customer-detail/' + customer_id+ '/';
fetch(url)
.then(resp => resp.json())
.then((item) => resolve(item))
.catch((err) => {
console.log(err)
reject(err)
})
})
}
test();
And the console looks like this:
All done []length: 0__proto__: Array(0)
1466
1663
I thought based on the promise logic, all done should have been read in the end, am I missing anything here?
It's hard to understand the first time, but here I go.
The simple answer is "You can't do that", javascript works with an 'event loop', it's like a thread containing all the tasks javascript is going to do, when you use an asynchronous task like 'search' it escapes the 'event loop' for being asynchronous . But the 'for loop' is not asynchronous so it will be processed in the 'Event Loop'.
This means that 'fetch' will exit the 'Event Loop' and when its request is completed it will return to the 'Event Loop', in this moment, the 'for loop' was terminate.
But don't worry, you can solve it, how? well, you can iterate the loop and save a new array of promise, await for all of them will complete and in this moment create your 'tr' row
Its an example with your code:
The first function could be like this:
const promises = [];
fetch(url)
.then(resp => resp.json())
.then((item) => {
const list = item;
for (let i in list) {
promises.push(getCustomer(list[i].customer_id))
}
Promise.all(promises).then((resolveAllData) => {
//Do all that you need with your data
//Maybe you can use a for loop for iterate 'responseAllData
});
}).catch((err) => {
//manage your err
})
And the second function could be like this:
function getCustomer(customer_id) {
return new Promise((resolve, reject) => {
const url = 'http://127.0.0.1:8000/user-detail/' + customer_id + '/';
fetch(url)
.then(resp => resp.json())
.then((item) => resolve(item))
.catch((err) => {
//manage your err
reject(err)
})
})
}
A recommendation, learn how JavaScript works and its asynchronism, try not to use 'var', to get better performance and have no problems with scope
I have got a function that invokes when a button is clicked, and I am trying to fetch data through API upon clicking. and below is the function that gets triggered onclick. I have to fetch data through API once and then based on a field from the fetched data, I need to fetch another data from another data table. so, I have designed my coding as below, and my console looks like this:
And the console looks like this:
All done []length: 0__proto__: Array(0)
1466 (customer_id logged during for loop)
1663 (customer_id logged during for loop)
I thought based on the promise logic, all done should have been read in the end, am I missing anything here?
so Ideally, Alldone console should have invoked at the end containing data fetched based on customer_ids 1466, 1663.
I am not sure what I am missing, I am new to javascript as well as stack overflow, so detailed answer would be so much appreciated.
function test(pmt_id, crm_supplier_id) {
const url = 'http://127.0.0.1:8000/creditor-detail/'+pmt_id+'/supplier/'+crm_supplier_id+'/'
let promises = [];
const results = fetch(url)
.then(resp => resp.json())
.then(function (item) {
var list = item;
for (var i in list) {
promises.push(getCustomer(list[i].customer_id));
console.log(list[i].customer_id)
}
})
Promise.all(promises)
.then((results) => {
console.log("All done", results);
})
.catch((e) => {
console.log(err)
});
}
//second function
function getCustomer(customer_id) {
return new Promise((resolve, reject) => {
const url = 'http://127.0.0.1:8000/customer-detail/' + customer_id+ '/';
fetch(url)
.then(resp => resp.json())
.then((item) => resolve(item))
.catch
You run the Promise.all part before you even push anything into the promises array, since the latter part runs asynchronously. You'd need to move the Promises.all part into a .then:
const results = fetch(url)
.then(resp => resp.json())
.then(function (item) {
var list = item;
for (var i in list) {
promises.push(getCustomer(list[i].customer_id));
console.log(list[i].customer_id)
}
})
.then(() => Promise.all(promises))
.then((results) => {
console.log("All done", results);
})
.catch((e) => {
console.log(err)
});
Also, just writing .catch does nothing, you need to call it and pass a handler:
fetch(url)
.then(resp => resp.json())
.then((item) => resolve(item))
.catch(e => console.error('Something failed!', e)
Plus, it makes no sense to use new Promise in getCustomer, just return the promise that comes from fetch:
function getCustomer(customer_id) {
const url = 'http://127.0.0.1:8000/customer-detail/' + customer_id+ '/';
return fetch(url)
.then(resp => resp.json())
.then((item) => resolve(item))
.catch(e => console.error('Something failed!', e));
}
But in general, I'd recommend looking into async/await, it would greatly clean up your code and make it much easier to read and understand for yourself:
async function test (pmt_id, crm_supplier_id) {
try {
const url = `http://127.0.0.1:8000/creditor-detail/${encodeURIComponent(pmt_id)}/supplier/${encodeURIComponent(crm_supplier_id}/`
const listResponse = await fetch(url)
if (listResponse.status !== 200) throw new Error(`List request failed with status ${indexResponse.status}`)
const listResult = await listResponse.json()
const results = await Promise.all(listResult.map(c => getCustomer(c.customer_id)))
console.log('All done', results)
} catch (err) {
console.log(err)
}
}
async function getCustomer (customer_id) {
const customerResponse = await fetch(`http://127.0.0.1:8000/customer-detail/${encodeURIComponent(customer_id)}/`)
if (customerResponse.status !== 200) throw new Error(`Customer request failed with status ${customerResponse.status}`)
const customerResult = await customerResponse.json()
return customerResult
}
I've been stuck working on this exercise FOR AGES, I'm finally throwing in the towel and asking for some help.
Make an AJAX call to the Star Wars API [https://swapi.co/] and get the opening crawl for each film in the series. Once you have finished that, loop through the array of planets for each movie and make more AJAX calls to collect the name of each planet, organized by film. Then, console log an array of objects in which each object contains the opening crawl for a specific movie, along with the names of every planet featured in that movie.
I've read a few articles and watched a few videos on Asynchronous js, and I 'think?', I sorta get it. This really doesn't want to work though.
var promiseList = [];
var fetchArray = function(arr) {
promiseArr = arr
.map(url => fetch(url)
.then(res => res.json())
.then(planet => planet.name)
);
Promise.all(promiseArr)
.then(data => console.log(data));
}
// fetchArray doesn't work at all. Curious if it's possible to run this code as it's technicall synchronous (idk).
for (let number = 1; number < 8; number ++) {
var t = fetch(`https://swapi.co/api/films/${number}/`)
.then(res => res.json())
promiseList.push(t)
}
console.log("NAHANH", promiseList)
// Prints out [[object Promise] { ... }, [object Promise] { ... }, [object Promise] { ... }, [object Promise] { ... },
[object Promise] { ... }, [object Promise] { ... }, [object Promise] { ... }]
Promise.all(promiseList)
.then(films => films.map(film => ({
"title": film.title,
"planets": film.planets,
"opening_crawl": film.opening_crawl,
})))
// Works up untill this point, this next then doesn't work, my aim is to alter the film.plants property in every
array object and actually fetch the planet rather than just the url!
// An example print out would be...
// {
// opening_crawl: "Luke Skywalker has vanis...",
// planets: ["https://swapi.co/api/planets/61/"],
// title: "The Force Awakens"
// }]
.then(films => films.map(film => {
film.planets = film.planets
.map(url => fetch(url)
.then(res => res.json())
.then(planet => planet.name)
.catch(error => console.log(error.message))
);
}
.then(data => console.log(data))
// Which would then finally resolve to (this is what I want but cannot get at the moment!)
// {
// opening_crawl: "Luke Skywalker has vanis...",
// planets: ["Yavin IV"],
// title: "The Force Awakens"
// }]
It almost works I can return an object. The fetch arrays doesn't work at all. and my second alteration attempting to retrieve the planet name doesn't work.
First, rename function fetchArray as fetchPlanetNames, as that's what it does, and add a couple of missing returns. Then you have a worker function that makes the main code block much simpler ( as was your intention :-) ).
In the main code block, fetchPlanetNames(...) returns Promise, not Array, therefore, inside the films.map() functor, you need fetchPlanetNames(film.planets).then(planets => {/* compose the required film object here */}). It's a kind of "inside-out" version of what you tried.
var fetchPlanetNames = function(arr) {
return Promise.all(arr.map(url => { // return the Promise returned by Promise.all(...).then(...)
// ^^^^^^
return fetch(url)
.then(res => res.json())
.then(planet => planet.name);
}))
.then(data => {
console.log(data);
return data; // Remember to return `data`, otherwise undefined will be dlivered.
// ^^^^^^^^^^^^
});
};
var promiseList = [];
for (let number = 1; number < 8; number ++) {
promiseList.push(fetch(`https://swapi.co/api/films/${number}/`).then(res => res.json()));
}
Promise.all(promiseList)
.then(films => {
return Promise.all(films.map(film => {
// Here, a nested Promise chain allows `film` to remain in scope at the point where 'planets' become available.
return fetchPlanetNames(film.planets)
.then(planets => {
return {
'title': film.title,
'planets': planets,
'opening_crawl': film.opening_crawl
};
});
}));
})
.then(data => console.log(data));
I'm trying to prefetch multiple image before navigating to another screen, but returnedStudents all undefined.
prepareStudentImages = async (students) => {
let returnedStudents = students.map(student => {
Image.prefetch(student.image)
.then((data) => {
...
})
.catch((data) => {
...
})
.finally(() => {
return student;
});
});
await console.log(returnedStudents); // ----> all items undefined
}
There are a couple of things to fix with this:
1) Your map() function does not return anything. This is why your console log is undefined.
2) Once your map functions work, you are logging an array of promises. To deal with multiple promises (an array), you can use Promise.all().
So I think to fix this, you can do:
prepareStudentImages = async (students) => {
const returnedStudents = students.map(student =>
Image.prefetch(student.image)
.then((data) => {
...
})
.catch((data) => {
...
})
.finally(() => {
return student
})
)
console.log(returnedStudents) // log the promise array
const result = await Promise.all(returnedStudents) // wait until all asyncs are complete
console.log(result) // log the results of each promise in an array
return result
}
I have a function that fetches data from the url and is supposed to return it:
const fetchTableData = () => {
fetch('https://api.myjson.com/bins/15psn9')
.then(result => result.json())
.then(data => {
return data;
})
}
export default fetchTableData;
The problem is that when i import this function and try to use it, it always returns undefined.
When i console log the data inside the function itself, you can see it is available. The function just doesn't work when i try to import it.
What is the problem here? Why does it work that way?
Try this =) You have to return something from the fetchTableData function also.
const fetchTableData = () => {
const fetchedData = fetch('https://api.myjson.com/bins/15psn9')
.then(result => result.json())
.then(data => {
return data;
})
return fetchedData;
}
export default fetchTableData;
Or you can just return it like this:
const fetchTableData = () => {
return fetch('https://api.myjson.com/bins/15psn9')
.then(result => result.json())
.then(data => {
return data;
})
}
export default fetchTableData;
In your code you were not returning from the fetchTableData function. Only from the the second then() callback. When a function has no return value, undefined will be returned.
Try this instead:
const fetchTableData = () => {
const myResponse = fetch('https://api.myjson.com/bins/15psn9')
.then(result => result.json())
.then(data => {
return data;
})
return myResponse;
}
export default fetchTableData;
What now happens is the following:
The response return by the second then() function is returning the data.
We are saving this data in a variable, named myResponse.
We are now returning this value from the function fetchTableData.
You need to either store data in a global variable or assign any variable to fetch to get return data.
//First way
fetch('https://api.myjson.com/bins/15psn9')
.then(result => result.json())
.then(data => {
console.log("data",data);
});
//Second way
let binData = null;
fetch('https://api.myjson.com/bins/15psn9')
.then(result => result.json())
.then(data => {
binData = data;
console.log("binData", binData);
});
Here is the working example.