To make my code cleaner I want to use fetched API data in a few different functions, instead of one big. Even though I 've did manage to reffer to that data in other functions, the problem is the API im a fetching throws different, randomized results every time it is called. And so the output from userData() does not equal that from userData2(), even though my intention is different and I'd like the result variable contents to be the same between functions.
const getData = () =>
fetch("https://opentdb.com/api.php?amount=10").then((response) =>
response.json()
);
const useData = async () => {
const result = await getData();
console.log(result);
};
const useData2 = async () => {
const result = await getData();
console.log(result);
};
Your getData() function returns a promise. One fun fact about promises is that while they can only resolve once, that resolved value can be accessed and used as many times as you want.
const dataPromise = getData();
const useData = async () => {
const result = await dataPromise;
console.log(result);
};
const useData2 = async () => {
const result = await dataPromise;
console.log(result);
};
Using await resolves the promise value, the equivalent of...
dataPromise.then((result) => {
console.log(result);
});
// or `dataPromise.then(console.log)` if you like brevity
I like to point this out about the fetch-api... you should always check the Response.ok property
const getData = async () => {
const res = await fetch("https://opentdb.com/api.php?amount=10");
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`);
}
return res.json();
};
Related
I am doing an API call which is returning IDs and based on number of ids I am doing another call and trying to combine the responses but I am stuck with async issues.
const SearchUser = async () => {
try {
const response = await getSearchUsers();
const ids = response.data?.map((user) => user.userId);
await ids.forEach(async (id) => {
const result = await getUserInfo(id);
setRNOUsers(...result);
// combine result in one state
});
} catch (error) {
setSearching(false);
}
};
useEffect(() => {
SearchUser();
console.log('RNOUsers', RNOUsers); // this is empty and runs even before callng api
}, []);
How can handle this?
You can use Promise.all to wait for all responses, and then set them together with setRNOUsers
const SearchUser = async () => {
try {
const response = await getSearchUsers();
const ids = response.data?.map((user) => user.userId);
const responses = await Promise.all(ids.map(id => getUserInfo(id)))
setRNOUsers(...responses.flatMap(x => x));
} catch (error) {
setSearching(false);
}
};
useEffect(() => {
SearchUser();
console.log('RNOUsers', RNOUsers);
}, []);
Side note, the problem with console.log('RNOUsers', RNOUsers) is setRNOUsers (initialized by useState) is asynchronous. Besides that, your API calls are also asynchronous, so you cannot get values from RNOUsers immediately in useEffect. If you want to see data in that log, you should wait until the state is updated and your component gets re-rendered with your latest data.
So I'm using the API of National Weather Service to create a weather app. But the fetched data is very complicated, and I need to fetch two different APIs. So I want to write a customized async function that returns a promise, which resolves to an object that contains only the necessary data I need.
I came up with something like this:
async function fetchWeatherAPI(){
//data that I need
let data = {};
try {
const res1 = await fetch(url1);
const result1 = await res1.json();
data = {...data , result1.usefulData};
} catch(error){}
try {
const res2 = await fetch(url2);
const result2 = await res2.json();
data = {...data, result2.usefulData};
} catch(error){}
return new Promise((resolve,reject)=>{
resolve(data);
})
}
This code is working for me. But the problem is, what if the APIs reject? How can I handle the error so that I can display the error message in the returned promise? I may want something like this:
return new Promise((resolve,reject)=>{
if(...) reject(errrorMessage);
resolve(data);
})
Just do return data. You're already inside an async function and the return value from that function will be the resolved value of the promise that the async function already returns:
async function fetchWeatherAPI(){
//data that I need
let data = {};
const res1 = await fetch(url1);
const result1 = await res1.json();
data = {...data , result1.usefulData};
const res2 = await fetch(url2);
const result2 = await res2.json();
data = {...data, result2.usefulData};
return data;
}
In addition, your catch blocks are silently eating errors and attempting to continue as if nothing went wrong. You probably just want the error to propagate back to the caller so if you just remove your try/catch blocks, that's what will happen.
And, the return new Promise() you have is entirely superfluous and unnecessary (referred to as an anti-pattern). You can just remove it and return data instead.
Note: Since the code you show does not show any dependency between your first and second fetch() calls, you could run them in parallel and perhaps finish them faster.
async function fetchWeatherAPI(){
//data that I need
let data = {};
const [result1, result2] = await Promise.all([
fetch(url1).then(r => r.json()),
fetch(url2).then(r => r.json())
]);
return {...data, result1.usefulData, result2.usefulData};
}
Or, I often use a helper function in my code when doing a lot of fetch() calls:
function fetchJson(...args) {
return fetch(...args).then(r => r.json());
}
async function fetchWeatherAPI() {
//data that I need
let data = {};
const [result1, result2] = await Promise.all([
fetchJson(url1),
fetchJson(url2)
]);
return { ...data, result1.usefulData, result2.usefulData };
}
I have an array and I am trying to perform an api call on each item.
Like so -
shoppingData.items.map(item => {
getItemInformation(item.id)
.then(response => response.json())
.then(data => {
JSON.parse(data.text);
});
getItemInformation is my api call -
export async function getItemInformation(id) {
try {
const req = await fetch(`**api url**`);
return await req;
} catch (e) {
console.error(e);
return 'Error';
}
}
However, once I have parsed the data, I would like to append it to a new array. This new array will then be used to render a component later down the page like so -
{newArray?.map((item, index) => (
<ShoppingItem key={index} itemDescription={item.description} itemCatergory={item.catergory} />
))}
Im having issues doing this as I have been trying to do it in a useEffect as ideally I need it to happen when the page renders. Furthermore, I tried having newArray as a state e.g const [newArray, setNewArray] = useState([]) but because I am appending items to an array, setNewArray wouldn't allow me to do this.
use Promise.all
const allItems = await Promise.all(
shoppingData.items.map(
item => getItemInformation(item.id)
.then(response => response.json())
)
);
You could also simplify this a bit by putting all the "asyncy" stuff into your getItemInformation method
const allItems = await Promise.all(
shoppingData.items.map(
item => getItemInformation(item.id)
)
);
and
export async function getItemInformation(id) {
try {
const req = await fetch(`**api url**`);
const json = await req.json();
return json;
} catch (e) {
console.error(e);
return 'Error';
}
}
Live example usiong JSONPlaceholder demo api:
async function getItemInformation(id) {
console.log("getItemInformation",id);
try {
const req = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
const json = await req.json();
return json;
} catch (e) {
console.error(e);
return 'Error';
}
}
(async function testIt() {
const shoppingData = {
items: [{
id: 1
}, {
id: 2
}, {
id: 3
}]
};
const allItems = await Promise.all(
shoppingData.items.map(
item => getItemInformation(item.id)
)
);
console.log(allItems);
})()
You can easily call setNewArray(allItems) in react useEffect using this code (basically where I did consolew.log(allItems) above.
I am not sure how do you append the data to your array while you are calling async function in .map without waiting for the result.
I assume you may be trying something like this:
const parsedData = shoppingData.items.map(item => {
getItemInformation(item.id)
.then(response => response.json())
.then(data => {
JSON.parse(data.text);
});
setNewArray(parsedData);
There are two points to note. Firstly getItemInformation() is called on each item async so the order of parsedItem may not be what your expected. Secondly setNewArray() is called while parsedData's async calls may not finish.
One possible way you can try, if you really want to use .map, is using Promise.all(). Promise.all() resolves when all its promises are resolved:
const getParsedData = async() => {
return Promise.all(shoppingData.items.map(item => getItemInfo.......));
}
const data = await getParsedData();
then append data to your array.
Another simpler solution is using for-loop and await:
for (let i=0;i<shoppingData.items.length;i++){
const parsedItem = await getItemInformation(shoppingData.items[i]).........
newArray.push(parsedItem);
}
I am trying to understand async calls using async/await and try/catch.
In the example below, how can I save my successful response to a variable that can be utilized throughout the rest of the code?
const axios = require('axios');
const users = 'http://localhost:3000/users';
const asyncExample = async () =>{
try {
const data = await axios(users);
console.log(data); //200
}
catch (err) {
console.log(err);
}
};
//Save response on a variable
const globalData = asyncExample();
console.log(globalData) //Promise { <pending> }
1) Return something from your asyncExample function
const asyncExample = async () => {
const result = await axios(users)
return result
}
2) Call that function and handle its returned Promise:
;(async () => {
const users = await asyncExample()
console.log(users)
})()
Here's why should you handle it like this:
You can't do top-level await (there's a proposal for it though);
await must exist within an async function.
However I must point out that your original example doesn't need async/await
at all; Since axios already returns a Promise you can simply do:
const asyncExample = () => {
return axios(users)
}
const users = await asyncExample()
try..catch creates a new block scope. Use let to define data before try..catch instead of const, return data from asyncExample function call
(async() => {
const users = 123;
const asyncExample = async() => {
let data;
try {
data = await Promise.resolve(users);
} catch (err) {
console.log(err);
}
return data;
};
//Save response on a variable
const globalData = await asyncExample();
console.log(globalData);
// return globalData;
})();
I had same issue with you and found this post. After 2 days of trying I finally found a simple solution.
According to the document of JS, an async function will only return a Promise object instead of value. To access the response of Promise, you have to use .then()method or await which can return the resulting object of Promise is instead of Promise itself.
To change variables from await, you have access and change the variable you want to assign within the async function instead of return from it.
//Save response on a variable
var globalData;
const asyncExample = async () =>{
try {
const data = await axios(users);
globalData = data; // this will change globalData
console.log(data); //200
}
catch (err) {
console.log(err);
}
};
asyncExample();
But if you do this, you may get an undefined output.
asyncExample();
console.log(globalData) //undefined
Since asyncExample() is an async function, when console.log is called, asyncExample() has not finished yet, so globalData is still not assigned. The following code will call console.log after asyncExample() was done.
const show = async () => {
await asyncExample();
console.log(globalData);
}
show();
Because the events are happening asynchronously you need to tie in a callback/promise. I'm going to assume it returns a promise.
const axios = require('axios');
const users = 'http://localhost:3000/users';
const asyncExample = async () =>{
try {
const data = await axios(users);
console.log(data); //200
}
catch (err) {
console.log(err);
}
};
//Save response on a variable
const globalData = asyncExample().then( (success, err) => {
if (err) { console.error(err); }
console.log(success)
}
Just use a callback/promise (cascading programming):
axios(users).then(function(response) {
const globalData = response;
console.log(globalData)
});
I have two files that are arrays, and i want to load them from a fetch. I have an async function that fetch the files:
async function getData(file) {
const data = await fetch(`./assets/resources/${file}.json`);
return await data.json()
}
Then here is where i assign the variables to the return fo this fetch:
let notes = getData("notes").then(res => res)
let pentagrama = getData("pentagrama").then(res => res)
But with this all i get is:
from google chrome console
How can i actually get the value?
The result of getData is always a Promise that resolves to your data. To access the values, you can use async/await:
(async () => {
let notes = await getData("notes");
let pentagrama = await getData("pentagrama");
// use them here
})();
Alternatively, you can use Promise.all to wait for both promises to resolve, and then access the received data:
let notesP = getData("notes");
let pentagramaP = getData("pentagrama");
Promise.all([noteP, pentagramaP]).then(res => {
let notes = res[0];
let pentagrama = res[1];
// use them here
});
ASYNC
AWAIT
This will work for you if you just want to check the response in your Google Chrome console because in the console you can use await without an async function which probably could be because everything executed in the console is wrapped in an async function by default(just a speculation).
ONLY WORKS IN CONSOLE:
const getData = (file) => (
fetch(`./assets/resources/${file}.json`).then(data => data.json());
)
let notes = await getData("notes")
let pentagrama = await getData("pentagrama")
But if you want to get this working in an application, remember that you ALWAYS need to wrap an await inside async
TO GET IT WORKING IN AN APPLICATION:
const getData = async (file) => (
await fetch(`./assets/resources/${file}.json`).then(data => data.json());
)
const wrapperFunc = async () => {
let notes = await getData("notes")
let pentagrama = await getData("pentagrama")
}