I wanted to run different error handling with React Promise.
API 1 and 2 should have different error handlings.
Execute APIs all at once to save time.
Run different error handling statements for each API as soon as possible, without waiting for the others.
Each API should continue even if one fails.
How can this be done?
Reference:
Fetch API requesting multiple get requests
Promise.all([
fetch(api1).then(value => value.json()),
fetch(api2).then(value => value.json())
])
.then((value) => {
console.log(value)
//json response
})
.catch((err) => {
console.log(err);
});
Promise.all is just wrapping up whatever promises you give it - so there's no reason you couldn't handle the errors separately for each one. For example, you could create a separate function for each of the fetches - you could even throw a custom error here that dictates some sort of "followUp" action to do, or identifies where the error is from, or anything (you can throw anything in javascript):
const fetchFromApi1 = async () => {
try {
const response = await fetch(api1);
return response.json();
} catch (err) {
console.log('API 1 failed');
// Throw a custom error
throw {
errorSource: 'API_CALL_1',
message: 'API call 1 failed',
};
}
};
const fetchFromApi2 = async () => {
// ----- 8< -----
};
Then you can just combine them in your your Promise.all - if you've thrown a custom error as above, you can use that to work out what to do:
const fetchAllTheThings = async () => {
try {
const [response1, response2] = await Promise.all([
fetchFromApi1(),
fetchFromApi2(),
]);
} catch (err) {
const { errorSource, message } = err;
// do something....
}
};
Edit
If you want to know which promise failed at the point of calling, you're probably better off using allSettled -
const fetchAllTheThings = async () => {
const [result1, result2] = await Promise.allSettled([
fetchFromApi1(),
fetchFromApi2(),
]);
if (result1.status === 'rejected') {
// Sad for promise 1
}
if (result2.status === 'rejected') {
// Sad for promise 2
}
};
const p1 = new Promise((res, rej) => {
setTimeout(() => {
res("p1 success")
}, 1000)
})
const p2 = new Promise((res, rej) => {
setTimeout(() => {
res("p2 success")
}, 3000)
})
const p3 = new Promise((res, rej) => {
setTimeout(() => {
rej("p3 failed")
}, 1000)
})
const p4 = new Promise((res, rej) => {
setTimeout(() => {
rej("p4 failed")
}, 2000)
})
Promise.allSettled([p1, p2, p3, p4])
.then(console.log)
Related
I want to wait until all axios in useEffect are finished.
UseEffect:
useEffect(() => {
async function getHomePageContent() {
await HomePageServices.getSliderContent().then((response) => {
setSliderProduct(response.data);
});
await HomePageServices.getRecommendedProducts().then((response) => {
setRecommendedProducts(response.data);
});
await HomePageServices.getMostOrderProducts().then((response) => {
setMostOrderProducts(response.data);
});
await HomePageServices.getMostRatedProducts().then((response) => {
setMostRatedProducts(response.data);
});
}
getHomePageContent().catch((error) => {
console.log(error)
});
}, []);
Class:
class HomePageServices{
async getSliderContent(){
return await axios.get(baseURL+"/slider")
}
async getMostRatedProducts(){
return await axios.get(baseURL+"/mostRatedProducts")
}
async getMostOrderProducts(){
return await axios.get(baseURL+"/mostOrderProduct")
}
async getRecommendedProducts(){
return await axios.get(baseURL+"/recommendedProduct")
}
}
Can someone explain to me how to wait for all axios to end, and if one failed, how to find out which one it was?
Try using Promise.allSettled() which takes an iterable (e.g. array) of promises and resolves into array of results of each of them.
Results are represented as objects with status key, which can be rejected or fulfilled. The second key of the object is either value containing the resolved value, or reason in case promise was rejected.
Taking this, then your code in useEffect might be something like this:
useEffect(() => {
const getHomePageContent = async () => ({
const promises = [
HomePageServices.getSliderContent(),
HomePageServices.getRecommendedProducts(),
HomePageServices.getMostOrderProducts(),
HomePageServices.getMostRatedProducts()
];
const data = await Promise.allSettled(promises);
const [slider, recommended, mostordered, mostrated] = data;
// result for each of promise
console.log(slider); // { status: 'fulfilled', value: 123 }
console.log(recommended) // { status: 'rejected', reason: 'blah'}
});
getHomePageContent().catch((er) => console.log(er))
}, [])
According to the documentation from firebase you can get a document very simply by using get()
But for some reason in my code it always displays that there's no such document, even though it does exist, this is what I'm doing:
useEffect(() => {
console.log(user, "This is the user UID:"+user.uid)
const userDoc = db.collection('usuarios').doc(user.uid);
const doc = userDoc.get();
if (!doc.exists) {
console.log('No such document!');
}
else {
userDoc
.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setUserData(tempData);
})
}
}, [user]);
This is what the console.log() shows:
This is how it looks in firebase:
const doc = userDoc.get();
if (!doc.exists) {
.get returns a promise, so you're checking the .exists property on a promise, which is undefined. You will need to wait for that promise to resolve, either with .then:
userDoc.get().then(doc => {
if (!doc.exists) {
// etc
}
});
Or by putting your code in an async function and awaiting the promise:
const doc = await userDoc.get();
if (!doc.exists) {
// etc
}
If you're using the firebase 8 web version, the userDoc.get() returns a promise, not the document:
userDoc.get().then((doc) => {
if (!doc.exists) {
console.log('No such document!');
} else {
const tempData = [];
const data = doc.data();
tempData.push(data);
setUserData(tempData)
console.log('it worked')
}
}).catch((error) => {
console.log("Error getting document:", error);
});
You can get more info about promises in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises.
In your code you are using the get method to fetch user data and get doesn't provide a snapshot. also, you missed that get() will return a promise so you have to handle using async-await or .then etc.
useEffect(() => {
console.log(user, "This is the user UID:"+user.uid);
getUser(user.uid).then(userData => {
setUserData(userData);
});
}, [user]);
const getUser = async (id) => {
try {
const user = await db.collection('usuarios').doc(id).get();
const userData = user.data();
return userData;
} catch (err){
console.log('Error during get user, No such document!');
return false;
}
I have a useEffect(.., []) initialization sequence which involves 2 operations, each an await-blocked sync Axios call.
I verified that the 1st Axios call await call is not honored and the flow jumps to the 2nd useEffect operation, getFlag(), right after the await axios.get() line. Does anyone know why?
useEffect(() => {
getAgreements(); // Step 1
getFlag(); // Step 2
}, []);
const getAgreements = async () => {
const url = '/getAgreements';
try {
const response = await axios.get(url); // This 'await' is not honored, jumps to getFlag()
setAgreementsList(response.data);
}
catch (error) {
setErrorObj({message: error.message, location: 'Agreements.js: getAgreements'});
}
finally {
setIsLoading(false);
}
}
const getFlag = async () => {
const url = '/getNewAgreementIndicator';
try {
const response = await axios.get(url);
setNewAgreementFlag(response.data);
}
catch (error) {
setErrorObj({message: error.message, location: 'Agreements.js: getNewAgreementIndicator'});
}
finally {
setIsLoading(false);
}
}
When I rewrite the code below with .then() I see that in the 2nd fetch, the await is not honored, and it goes to console.log() before the result of the 2nd fetch is returned. Can anyone clarify?
useEffect(() => {
getAgreements()
.then(() => {
getFlag()
})
.then(() => {
console.log('test');
})
}, []);
Wouldnt it be better to just have 2 use effects rather than all of them in the one that happens on mount? Have another useEffect which gets triggered when the agreementsList gets update
useEffect(() => {
getAgreements(); // Step 1
}, []);
useEffect(() => {
if(agreementsList){
getFlag(); // Step 2
}
}, [agreementsList]);
As described in comments between my code snippet, the asynchronicity is not working as expected. For each id, an object/item should return but it only returns one item since my async await isn't implemented properly. What could be a possible workaround?
Thanks in advance
useEffect(() => {
axios.get('url-here').then((res) => {
res.data.favProperties?.map((el) => {
console.log(el) // this returns multitple id's of saved/liked items
axios.get('url-here').then(async (r) => {
if (r.data) {
console.log(r.data) // Problem starts here
// This returns the full object of the liked items
// But only one object is returned, not every object for which an id was stored
await storageRef
.child(r.data.firebaseRef + '/' + r.data.images[0])
.getDownloadURL()
.then((url) => {
// Here i need to fetch the image for each object
console.log(url)
})
.catch((err) => console.log(err))
}
})
})
})
}, [])
I think breaking down your operations into functions will prevent this Promise Hell. I would recommend using async await for these kinda operations. Also I was confused about the last part of console logging the download URL, by my guess you're trying to save all the download URLs for these liked items in an array.
useEffect(() => {
firstFunction();
}, []);
const firstFunction = async () => {
const { data } = await axios.get("url-here");
const favProperties = data.favProperties;
const fetchedUrls = await Promise.all(
favProperties?.map(async (el) => (
await secondFunction(el.id) /** use el to pass some ID */
))
);
};
const secondFunction = async (someId) => {
/** your second URL must point to some ID (or some parameters) specific API otherwise
running same op in a loop without variations doesn't make any sense */
const { data } = await axios.get(`some-other-url/${someId}`);
if (data) {
console.log(data);
const fetchedUrl = await storageThing(data);
return fetchedUrl;
}
};
const storageThing = async ({ firebaseRef, images }) => {
try {
const downloadURL = await storageRef
.child(firebaseRef + "/" + images[0])
.getDownloadURL();
console.log(downloadURL);
return downloadURL;
} catch (error) {
console.log(error);
return '';
}
};
This is a bit tricky to explain, but here is what I'm doing:
Trying to get json data from an async function called getJsonData() until data is fetched.
After getting the data correctly, I want to get another set of json data from getOtherJsonData()
The following code gets me the first set of data (getJsonData) correctly even after X failures. (if any)
It doens't however get the second set of data (getOtherJsonData) all the time as an error could occur. I want to keep re-execution the bloc of code marked below until the second set of data is returned correctly.
...
import React, {useState, useEffect} from 'react';
import {getJsonData} from './getJsonData';
imoport {getOtherJsonData} from './getOtherJsonData';
const myApp = () => {
const [errorFetchedChecker, setErrorFetchedChecker] = useState(false);
const [isLoading,setIsLoading] = useState(true);
const [data,setData] = useState(null);
const updateState = jsonData => {
setIsloading(false);
setData(jsonData);
};
useEffect(() => {
getJsonData().then(
data => {
updateState(data);
// This is the bloc I want to keep re-executing
//
getOtherJsonData(data.title).then(
otherData => {
updateOtherState(otherData);
console.log("Updated with no error);
},
otherError => {
console.log("Error, try getOtherJsonData again ?");
console.log("Can't try to refresh, no errorFetchedChecker for me :/ ");
}
//
// Until It doesn't return an error
},
error => {
console.log('Error fetching, re-trying to fetch thanks to errorFetchedChecker');
setErrorFetchedChecker(c => !c);
},
);
}, [errorFetchedChecker]);
return (
<View>
<Text>{state.data.title}</Text>
<Text>{data.data.completed}</Text>
</View>
);
}
Here's getJsonData() and getOtherJsonData()
export async function getJsonData() {
try {
let response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
let responseJson = await response.json();
return responseJson;
} catch (error) {
throw error;
// Should I just throw the error here ?
}
}
export async function getOtherJsonData(oldData) {
try {
let response = await fetch(`https://someOtherApilink/${oldData}`);
let responseJson = await response.json();
return responseJson;
} catch (error) {
throw error;
// Should I just throw the error here also ?
}
}
This is my other question which explains how to re-execute the first getJsonData() in case of failure.
Below is something I tried but gave me error about unhandled promises:
const subFunction(myTitle) => {
getOtherJsonData(myTitle).then(
otherData => {
updateOtherState(otherData);
console.log("Updated with no error);
},
otherError => {
console.log("Error, try getOtherJsonData again!");
subFunction(myTitle); //Gives Unhandled promise warning and no result
}
}
useEffect(() => {
getJsonData().then(
data => {
updateState(data);
// This is the bloc I want to keep re-executing
//
subFunction(data.title);
//
// Until It doesn't return an error
},
error => {
console.log('Error fetching, re-trying to fetch thanks to errorFetchedChecker');
setErrorFetchedChecker(c => !c);
},
);
}, [errorFetchedChecker]);
Note: Feel free to rephrase the title in any way, shape or form.
You can try to separate these two functions with using two useEffect, because now you'll have to repeat first request in case of second fail. Something like this:
useEffect(() => {
getJsonData().then(
data => {
updateState(data);
},
error => {
console.log('Error fetching, re-trying to fetch thanks to errorFetchedChecker');
setErrorFetchedChecker(c => !c);
},
);
}, [errorFetchedChecker]);
useEffect(() => {
// prevent request if there's no data
if (data) {
getOtherJsonData(data.title).then(
otherData => {
updateOtherState(otherData);
console.log("Updated with no error);
},
otherError => {
console.log("Error, try getOtherJsonData again ?");
console.log("Can't try to refresh, no errorFetchedChecker for me :/ ");
// you'll have to create one more state for that
setOtherErrorFetchedChecker(c => !c);
}
}
}, [data, otherErrorFetchedChecker])