Mapping fetched data - javascript

I'm trying to build a TreeNode function in react that builds a tree structure of some data.
I have fake data as a starting point:
const fakeData = {
"1234": {
id: "1234",
name: "Division",
address: "string",
childNodes: []
}
};
When I expand the "Division" node with the "id" 1234, I want to fetch the children of this node. So my fakeData expands to something like this:
I'm using the fetch() method to get data, but I'm unsure of how to pass that data to resolve() method of the Promise. Right now I'm doing this:
function fakeAjax(url, id) {
return new Promise((resolve, reject) => {
fetch(url)
.then(res => res.json())
.then(result => {
const nodes = result.map(node => node.id);
resolve(fakeData[id].childNodes.map(nodes));
});
});
}
function fetchChildNodes(id) {
return fakeAjax(`someUrl`, id);
}
function TreeNode({ id, name }) {
const [childNodes, setChildNodes] = useState(null);
// Toggle our display of child nodes
const toggleExpanded = useCallback(() => {
fetchChildNodes(id)
.then(nodes => setChildNodes(nodes.map(node => <TreeNode {...node} />)))
}
}, [childNodes]);
But this is not passing the relavant data to the resolve method. Am I doing something wrong?

A couple of things jump out:
Your code is using the explicit promise creation anti-pattern. fetch returns a promise, there's no need to wrap new Promise around it. It's not the problem, but it's a problem, not least because the way fakeAjax is written, if the ajax call fails, the promise returned by fakeAjax is never rejected (and never fulfilled, it just stays pending forever).
You're calling map passing in a non-function as the mapping function:
const nodes = result.map(node => node.id);
resolve(fakeData[id].childNodes.map(nodes));
// Here −−−−−−−−−−−−−−−−−−−−−−−−−−−−^
That doesn't make any sense. map accepts a function, not an array.
Your code is getting hit by the footgun in the fetch API that almost everyone else is also hit by: You need to check that the request worked at the HTTP level. fetch only rejects when there's a network error, not an HTTP error (more on my blog here).
I assume your ajax call returns actual node data that you should be passing directly to TreeNode, not modifying (and certainly not turning into an array of just id values).
So something like:
function fetchJSON(url) {
return fetch(url)
.then(res => {
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.json();
});
}
function fetchChildNodes(id) {
return fetchJSON(`someUrl`, id);
}
The mapping of node data to TreeNode is happening in TreeNode, so I don't think you need to do anything in fetchChildNodes.

Related

Javascript JSON Fetch with await returns data but its function return an empty array

I'm trying to fiddle with fetching data from public APIs and then showing that data in a React component, the react part is not important tho, this is primarly a js issue. I'm using PokeApi for learning purpose, I create a new object with the data I need from the request, and then push that object to an array that the function returns when called:
// fetchPoke() function
let pokemons = []
// IDs is just an array with 20 random numbers between a range
IDs.forEach(async (id) => {
let url = `https://pokeapi.co/api/v2/pokemon/${id}`
await fetch(url)
.then((res) => {
if (!res.ok) {
console.log(`${id} not found`)
} else {
return res.json()
}
})
.then((data) => {
let pokemon = {}
pokemon.id = data.id
pokemon.name = data.name
pokemon.image = data.sprites.other.dream_world.front_default
pokemons.push(pokemon)
})
})
// function returns the array of object
return pokemons
But whenever I call this function
let pokemons = fetchPoke()
And then log it
console.log(pokemons)
Although I can see the content, it says it's an array of 0:
In fact if I try to console log pokemons.length I get 0
What could cause this? Am I doing something wrong in my fetch request?
So, you create an empty array.
You loop through you loop through the array, firing-off a bunch of asynchronous requests that will, as a side-effect, populate the empty array when the promises complete.
You immediately return the array without waiting for the promises to complete.
The requests probably have not even left your machine by the time this happens.
The array is empty at this point.
If instead, you declare your function as async and you map your IDs to a new array of promises, then await them all with Promise.all, then the promises will be able to complete before the function returns, and the resolved value of Promise.all will be an array containing your pokemons.
async function getSomePokemons(IDs) { // note, this is an async function
const promises = IDs.map((id) =>
fetch(`https://pokeapi.co/api/v2/pokemon/${id}`)
.then((res) => {
if (!res.ok) {
console.log(`${id} not found`)
// implicitly resolves as undefined
} else {
return res.json()
}
})
.then((data) => (data ? { // data might be undefined
id: data.id,
name: data.name,
image: data.sprites.other.dream_world.front_default
} : undefined))
)
const pokemons = await Promise.all(promises);
return pokemons;
}

console log of returned value shows a unusable response even after chaining a then to it

Im trying to return a promise is a javascript file. However, there is a weird issue. So when I console log the returned value within the function, it shows the following:
const id = getAccounts()
.then(res => res.find(acc => acc.type === ACCOUNT_TYPES.STARTER))
.then((res) => { return res.id });
console.log(id.then(res => res))
Is there anything I am missing? Have been dealing with this and research for the whole day. If anyone can help, I would highly appreciate it!
Updated section:
const initialState = {
currentAccountId: id.then((res) => { return res; }) || ''
};
The return value of calling a Promise's .then is always another Promise. By setting currentAccountId to id.then, it will always be a Promise.
You need to call this.setState from inside the Promise's resolve function:
componentDidMount() {
getAccounts()
.then(res => res.find(acc => acc.type === ACCOUNT_TYPES.STARTER))
.then((res) => { this.setState({ currentAccountId: res }); });
}
Use componentDidMount, like the React docs suggest, to initiate an async request. "If you need to load data from a remote endpoint, this is a good place to instantiate the network request."
Original answer
id.then will always return a new Promise, and that's what you are logging. To log the actual value you can move the console.log inside the resolve function:
id.then(res => console.log(res))

Synchronously populate/modify an array and return the modified array inside a promise

I'm pretty new to ReactJS and redux, so I've never really had to work with this before. I'm retrieving data from an API call in my project. I want to modify the data by adding a new property to the object. However, because the code is not ran synchronously, the unmodified array is being returned (I assume) instead of the modified array.
export function loadThings() {
return dispatch => {
return dispatch({
type: 'LOAD_THINGS',
payload: {
request: {
url: API_GET_THINGS_ENDPOINT,
method: 'GET'
}
}
}).then(response => {
let things = response.payload.data;
// Retrieve all items for the loaded "things"
if(things) {
things.forEach((thing, thingIndex) => {
things[thingIndex].items = []
if (thing.hasOwnProperty('channels') && thing.channels) {
thing.channels.forEach(channel => {
if (channel.hasOwnProperty('linkedItems') && channel.linkedItems) {
channel.linkedItems.forEach(itemName => {
dispatch(loadItems(itemName)).then(
item => things[thingIndex].items.push(item) // push the items inside the "things"
)
})
}
})
}
})
}
things.forEach(data => console.log(data.items.length, data.items)) // data.items.length returns 0, data.items returns a populated array
return things // return the modified array
}).catch(error => {
//todo: handle error
return false
})
}
}
As you can see, I perform an API call which returns data named response. The array is populated with all "things". If things exists, I want to load extra information named "items". Based on the information in the things array, I will perform another API call (which is done by dispatching the loadItems function) which returns another promise. Based on the data in the results of that API call, I will push into the items property (which is an array) of the things object.
As you can see in the comments, if I loop through the things array and log the items property which I just created, it's basically returning 0 as length, which means the things array is being returned before the things array is being modified.
I would like to know two things:
What is causing my code to run async. Is it the
dispatch(loadItems(itemName)) function since it returns a promise?
How am I able to synchronously execute my code?
Please note: this function loadThings() also returns a promise (if you're not familair with redux).
You might be interested in knowing what I tried myself to fix the code
Since I fail to understand the logic why the code is ran async, I've been trying hopeless stuff. Such as wrapping the code in another Promise.all and return the modified array in that promise. I used the then method of that promise to modify the things array, which had the exact same result. Probably because return things is being executed outside of that promise.
I'd really love to know what is going on
Edit
I have added the loadItems() code to the question, as requested:
export function loadItems(itemName) {
return dispatch => {
const url = itemName ? API_GET_ITEMS_ENDPOINT + `/${itemName}` : API_GET_ITEMS_ENDPOINT;
return dispatch({
type: 'LOAD_ITEMS',
payload: {
request: {
url: url,
method: 'GET'
}
}
}).then(response => {
return response.payload.data
})
}
}
My approach would be to map over things, creating arrays of promises for all of their items wrapped in a Promise.all, which gives you an array of Promise.all's.
Then you return things and this array of promises in another Promise.all and in the next then block, you can just assign the arrays to each thing with a simple for loop:
export function loadThings() {
return dispatch => {
return dispatch({
type: 'LOAD_THINGS',
payload: {
request: {
url: API_GET_THINGS_ENDPOINT,
method: 'GET'
}
}
}).then(response => {
let things = response.payload.data;
// Retrieve all items for the loaded "things"
const items = things.map((thing) => {
const thingItems = []
if (thing.hasOwnProperty('channels') && thing.channels) {
thing.channels.forEach(channel => {
if (channel.hasOwnProperty('linkedItems') && channel.linkedItems) {
channel.linkedItems.forEach(itemName => {
thingItems.push(dispatch(loadItems(itemName)));
});
}
});
}
return Promise.all(thingItems);
});
return Promise.all([things, Promise.all(items)])
})
.then(([things, thingItems]) => {
things.forEach((thing, index) => {
thing.items = thingItems[index];
})
return things;
})
.catch(error => {
//todo: handle error
return false
})
}
}
Edit:
You need to push the dispatch(loadItmes(itemName)) calls directly into thingItems.
I guess you could refactor it like the following:
export function loadThings() {
return dispatch => {
return dispatch({
type: 'LOAD_THINGS',
payload: {
request: {
url: API_GET_THINGS_ENDPOINT,
method: 'GET'
}
}
}).then(response => {
let things = response.payload.data;
// Retrieve all items for the loaded "things"
if( things ) {
return Promise.all( things.reduce( (promises, thing) => {
if (thing.channels) {
thing.items = [];
promises.push( ...thing.channels.map( channel =>
channel.linkedItems &&
channel.linkedItems.map( item =>
loadItems(item).then( result => thing.items.push( result ) )
) ).flat().filter( i => !!i ) );
}
return promises;
}, []) );
}
return things;
}).catch(error => {
//todo: handle error
return false
})
}
}
In case you would have things, it would check for the channels and the linkedItems for that channel, and create a promise that will push the result back to the thing.items array.
By returning the Promise.all, the continuation of the loadThings would only complete when the Promise.all was resolved. In case there are no things, just things gets returned (which would be a falsy value, so I am wondering how valid that statement could be)
I haven't actually tested the refactoring so there might be some brackets in need of adjusting to your situation, but I guess it gives you an idea?

How to order the order of returned API calls with generators?

I'm practicing some more advanced Javascript techniques, and came across generators and iterators as something I wanted to look into. I know that I'm doing this incorrectly, but I'm not really sure how to go about it.
The idea of my little program is this: I want to make API calls to the OpenWeather API for four (or more, but I'm testing with four) cities. The cities are stored in an array and one by one, the city is appended to the URL and a fetch request is sent. Each response is appended to an array and the array is sent to the client.
This was my original code:
// node/express setup here
const cities = ["London%2Cuk", "New York%2Cus", "Johannesburg%2Cza", 'Kingston%2Cjm']
const url = process.env.URL_BASE;
const headers = {
"X-RapidAPI-Host": process.env.HOST,
"X-RapidAPI-Key": process.env.API_KEY
}
const requestInit = { method: 'GET',
headers: headers
};
const fetchWeather = (ep) => {
const appendedURL = url + ep;
return fetch(appendedURL, requestInit)
.then(r => r.json());
}
app.get('/', (req, res, err) => {
const data = []
Promise.all(
cities.map( async (city) => {
await fetchWeather(city)
.then(returns => {
data.push(returns)
})
})
)
.then(() => {
res.send(data)
return data;
})
.catch(err => console.log(err))
})
Right? Solid, works ok. But now I'm stuck on how to order it. The way I would think to do this is to switch await fetchWeather(city) to yield fetchWeather(city) and have a generator manager that would continue calling next(city) until the array had completed, but I'm having an issue figuring out the pattern. I refactored the api call to a generator and am testing out a generator management function.
The paradigm I have based on my understanding is this:
First .next() starts the iteration
Second .next(args) passes the designated city to the first yield
Third .next() sends the yielded fetch request and should (ideally) return the response object that can be .then()'d.
Here is my tester generator code:
function *fetchWeather() {
for (let i = 0; i < cities.length; i++){
const appendedURL = url + (yield);
yield fetch(appendedURL, requestInit)
.then(r => {
return r.json()
});
}
}
const generatorManager = (generator) =>{
if (!generator) {
generator = fetchWeather();
}
generator.next()
generator.next(cities[i])
generator.next().value.then( e =>
console.log(e));
}
I'm getting an error:TypeError: Cannot read property 'then' of undefined And I'm not sure where I'm going wrong here with my logic. How do I refactor this to allow me to wait for specific promises if I can't individually pass known values? I know there has to be a way, but I'm missing something.
Thanks in advance.
I don't understand what benefit you hope to get from using a generator here, but the reason you're getting that error is you're doing one to many .next()'s
The first generator.next() runs fetchWeather until the first yield, which is the yield at the end of const appendedURL = url + (yield);. The return value from calling generator.next() in this case is { value: undefined, done: false }
After that, generator.next(cities[i]) resumes fetchWeather, with cities[i] being the result of the previous yield. The generator continues running, calling fetch, then calling .then on that promise, and then yielding the resulting promise. So the return value that generatorManager sees from doing generator.next(cities[i]) is { value: /* a promise object */, done: false }.
So to fix that error, you need to reduce the number of calls you're making to generator.next
generator.next()
generator.next(cities[i]).value.then(e =>
console.log(e));
As mentioned in the comments, the usual way i'd do this is map the cities to promises, and then do promise.all. For example:
Promise.all(
cities.map((city) => fetchWeather(city)) // note, this is the original fetch weather, not the generator
).then((data) => {
res.send(data);
return data;
})
.catch(err => console.log(err))

Returning data from Axios API [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 months ago.
I am trying to use a Node.JS application to make and receive API requests. It does a get request to another server using Axios with data it receives from an API call it receives. The second snippet is when the script returns the data from the call in. It will actually take it and write to the console, but it won't send it back in the second API.
function axiosTest() {
axios.get(url)
.then(function (response) {
console.log(response.data);
// I need this data here ^^
return response.data;
})
.catch(function (error) {
console.log(error);
});
}
...
axiosTestResult = axiosTest();
response.json({message: "Request received!", data: axiosTestResult});
I'm aware this is wrong, I'm just trying to find a way to make it work. The only way I can seem to get data out of it is through console.log, which isn't helpful in my situation.
The issue is that the original axiosTest() function isn't returning the promise. Here's an extended explanation for clarity:
function axiosTest() {
// create a promise for the axios request
const promise = axios.get(url)
// using .then, create a new promise which extracts the data
const dataPromise = promise.then((response) => response.data)
// return it
return dataPromise
}
// now we can use that data from the outside!
axiosTest()
.then(data => {
response.json({ message: 'Request received!', data })
})
.catch(err => console.log(err))
The function can be written more succinctly:
function axiosTest() {
return axios.get(url).then(response => response.data)
}
Or with async/await:
async function axiosTest() {
const response = await axios.get(url)
return response.data
}
Guide on using promises
Info on async functions
I know this post is old. But i have seen several attempts of guys trying to answer using async and await but getting it wrong. This should clear it up for any new references
UPDATE: May 2022
This answer is still having lots of interest and have updated it to use arrow functions
const axiosTest = async () {
try {
const {data:response} = await axios.get(url) //use data destructuring to get data from the promise object
return response
}
catch (error) {
console.log(error);
}
}
you can populate the data you want with a simple callback function,
let's say we have a list named lst that we want to populate,
we have a function that pupulates pupulates list,
const lst = [];
const populateData = (data) => {lst.push(data)}
now we can pass the callback function to the function which is making the axios call and we can pupulate the list when we get data from response.
now we make our function that makes the request and pass populateData as a callback function.
function axiosTest (populateData) {
axios.get(url)
.then(function(response){
populateData(response.data);
})
.catch(function(error){
console.log(error);
});
}
The axios library creates a Promise() object. Promise is a built-in object in JavaScript ES6. When this object is instantiated using the new keyword, it takes a function as an argument. This single function in turn takes two arguments, each of which are also functions — resolve and reject.
Promises execute the client side code and, due to cool Javascript asynchronous flow, could eventually resolve one or two things, that resolution (generally considered to be a semantically equivalent to a Promise's success), or that rejection (widely considered to be an erroneous resolution). For instance, we can hold a reference to some Promise object which comprises a function that will eventually return a response object (that would be contained in the Promise object). So one way we could use such a promise is wait for the promise to resolve to some kind of response.
You might raise we don't want to be waiting seconds or so for our API to return a call! We want our UI to be able to do things while waiting for the API response. Failing that we would have a very slow user interface. So how do we handle this problem?
Well a Promise is asynchronous. In a standard implementation of engines responsible for executing Javascript code (such as Node, or the common browser) it will resolve in another process while we don't know in advance what the result of the promise will be. A usual strategy is to then send our functions (i.e. a React setState function for a class) to the promise, resolved depending on some kind of condition (dependent on our choice of library). This will result in our local Javascript objects being updated based on promise resolution. So instead of getters and setters (in traditional OOP) you can think of functions that you might send to your asynchronous methods.
I'll use Fetch in this example so you can try to understand what's going on in the promise and see if you can replicate my ideas within your axios code. Fetch is basically similar to axios without the innate JSON conversion, and has a different flow for resolving promises (which you should refer to the axios documentation to learn).
GetCache.js
const base_endpoint = BaseEndpoint + "cache/";
// Default function is going to take a selection, date, and a callback to execute.
// We're going to call the base endpoint and selection string passed to the original function.
// This will make our endpoint.
export default (selection, date, callback) => {
fetch(base_endpoint + selection + "/" + date)
// If the response is not within a 500 (according to Fetch docs) our promise object
// will _eventually_ resolve to a response.
.then(res => {
// Lets check the status of the response to make sure it's good.
if (res.status >= 400 && res.status < 600) {
throw new Error("Bad response");
}
// Let's also check the headers to make sure that the server "reckons" its serving
//up json
if (!res.headers.get("content-type").includes("application/json")) {
throw new TypeError("Response not JSON");
}
return res.json();
})
// Fulfilling these conditions lets return the data. But how do we get it out of the promise?
.then(data => {
// Using the function we passed to our original function silly! Since we've error
// handled above, we're ready to pass the response data as a callback.
callback(data);
})
// Fetch's promise will throw an error by default if the webserver returns a 500
// response (as notified by the response code in the HTTP header).
.catch(err => console.error(err));
};
Now we've written our GetCache method, lets see what it looks like to update a React component's state as an example...
Some React Component.jsx
// Make sure you import GetCache from GetCache.js!
resolveData() {
const { mySelection, date } = this.state; // We could also use props or pass to the function to acquire our selection and date.
const setData = data => {
this.setState({
data: data,
loading: false
// We could set loading to true and display a wee spinner
// while waiting for our response data,
// or rely on the local state of data being null.
});
};
GetCache("mySelelection", date, setData);
}
Ultimately, you don't "return" data as such, I mean you can but it's more idiomatic to change your way of thinking... Now we are sending data to asynchronous methods.
Happy Coding!
axiosTest() needs to return axios.get, which in turn returns a Promise.
From there, then can be used to execute a function when said Promise resolves.
See Promise for more info.
Alternatively, await can be used from within the scope of some async function.
// Dummy Url.
const url = 'https://jsonplaceholder.typicode.com/posts/1'
// Axios Test.
const axiosTest = axios.get
// Axios Test Data.
axiosTest(url).then(function(axiosTestResult) {
console.log('response.JSON:', {
message: 'Request received',
data: axiosTestResult.data
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
IMO extremely important rule of thumb for your client side js code is to keep separated the data handling and ui building logic into different funcs, which is also valid for axios data fetching ... in this way your control flow and error handlings will be much more simple and easier to manage, as it could be seen from this
ok fetch
and this
NOK fetch
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
function getUrlParams (){
var url_params = new URLSearchParams();
if( window.location.toString().indexOf("?") != -1) {
var href_part = window.location.search.split('?')[1]
href_part.replace(/([^=&]+)=([^&]*)/g,
function(m, key, value) {
var attr = decodeURIComponent(key)
var val = decodeURIComponent(value)
url_params.append(attr,val);
});
}
// for(var pair of url_params.entries()) { consolas.log(pair[0]+ '->'+ pair[1]); }
return url_params ;
}
function getServerData (url, urlParams ){
if ( typeof url_params == "undefined" ) { urlParams = getUrlParams() }
return axios.get(url , { params: urlParams } )
.then(response => {
return response ;
})
.catch(function(error) {
console.error ( error )
return error.response;
})
}
// Action !!!
getServerData(url , url_params)
.then( response => {
if ( response.status === 204 ) {
var warningMsg = response.statusText
console.warn ( warningMsg )
return
} else if ( response.status === 404 || response.status === 400) {
var errorMsg = response.statusText // + ": " + response.data.msg // this is my api
console.error( errorMsg )
return ;
} else {
var data = response.data
var dataType = (typeof data)
if ( dataType === 'undefined' ) {
var msg = 'unexpected error occurred while fetching data !!!'
// pass here to the ui change method the msg aka
// showMyMsg ( msg , "error")
} else {
var items = data.dat // obs this is my api aka "dat" attribute - that is whatever happens to be your json key to get the data from
// call here the ui building method
// BuildList ( items )
}
return
}
})
</script>
After 6 hours of fluttering, I realized it was a one-line problem. If you are interfering with the axios life-cycle, you may have forgotten this line:
componentDidMount() {
this.requestInterceptor = axios.interceptors.request.use((request) => {
this.updateApiCallFor(request.url, true);
return request;
});
this.responseInterceptor = axios.interceptors.response.use((response) => {
this.updateApiCallFor(response.config.url, false);
return response; // THIS LINE IS IMPORTANT !
}, (error) => {
this.updateApiCallFor(error.config.url, false);
throw error;
});
async makes a function return a Promise
await makes a function wait for a Promise
code async/await
// https://www.npmjs.com/package/axios
const axios = require('axios')
/* --- */
async function axiosTest() {
let promiseAxios = axios.get( 'https://example.com' )
/* --- */
console.log( await promiseAxios )
}
/* --- */
axiosTest()
replit.com Stackoverflow - Returning data from Axios API
replit.com Stackoverflow - How to return values from async
code async/await with return
// https://www.npmjs.com/package/axios
const axios = require('axios')
/* --- */
async function axiosTest() {
console.log( await promiseAxios() )
}
/* --- */
axiosTest()
/* --- */
// create function for promise axios and return it
function promiseAxios() {
return axios.get( 'https://example.com' )
}
replit.com Stackoverflow - Returning data from Axios API - return
replit.com Stackoverflow - How to return values from async - return
Try this,
function axiosTest() {
axios.get(url)
.then(response => response.data)
.catch(error => error);
}
async function getResponse () {
const response = await axiosTest();
console.log(response);
}
getResponse()
It works, but each function where you want to get the response needs to be an async function or use an additional .then() callback.
function axiosTest() {
axios.get(url)
.then(response => response.data)
.catch(error => error);
}
async function getResponse () {
axiosTest().then(response => {
console.log(response)
});
}
getResponse()
If anyone knows a way to avoid this please do tell.
Also checkout Katsiaryna (Kate) Lupachova's article on Dev.to. I think it will help.
async handleResponse(){
const result = await this.axiosTest();
}
async axiosTest () {
return await axios.get(url)
.then(function (response) {
console.log(response.data);
return response.data;})
.catch(function (error) {
console.log(error);
});
}
You can find check https://flaviocopes.com/axios/#post-requests url and find some relevant information in the GET section of this post.
You can use Async - Await:
async function axiosTest() {
const response = await axios.get(url);
const data = await response.json();
}

Categories

Resources