Multiple API calls with Promise.all and dispatch an action - javascript

I want to call multiple API's and store each response data in an object then I want to dispatch this response object but I'm getting undefined.
Below is the code I tried. May I know where I'm doing wrong?
/* COMPONENT.JSX */
componentDidMount() {
callApis(this.props.products, this.props.profileId);
}
/* API.JS */
const getContactDetails = (http, profileId) =>
(http.get(`https://www.fakeurl.com/${profileId}/contact`));
const getProductDetails = (http, profileId) =>
(http.get(`https://www.fakeurl.com/${profileId}/product`));
const callApis = (products, profileId) => (dispatch) => {
const payload = new Map();
products.forEach((product) => {
const apis = [getContactDetails, getProductDetails];
apis.map(api => api(http, profileId));
Promise.all(apis)
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
});
dispatch({ type: FETCH_API_DATA, payload: payload });
}
I expect the dispatch will be called after all API's were resolved, get parsed, and map into the payload object then it should dispatch.

Array.map returns a new Array, which you are discarding
you're calling dispatch before any of the asynchronous code has run
A few minor changes are required
/* API.JS */
const getContactDetails = (http, profileId) => http.get(`https://www.fakeurl.com/${profileId}/contact`);
const getProductDetails = (http, profileId) => http.get(`https://www.fakeurl.com/${profileId}/product`);
const callApis = (products, profileId) => (dispatch) => {
const payload = new Map();
// *** 1
const outerPromises = products.map((product) => {
const apis = [getContactDetails, getProductDetails];
// *** 2
const promises = apis.map(api => api(http, profileId));
// *** 3
return Promise.all(promises)
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
}));
// *** 4
Promise.all(outerPromises)
.then(() => dispatch({
type: FETCH_API_DATA,
payload: payload
})
)
.catch(err => console.log(err));
}
rather than procucts.forEach, use products.map
capture the promises in apis.map to use in Promise.all
return Promise.all so the outer Promises can be waited for
Promise.all on the outer promises, to wait for everything to complete.

const callApis = (products, profileId) => async (dispatch) => { // use async function
const payload = new Map();
for (const product of products) {
const apis = [getContactDetails, getProductDetails];
apis.map(api => api(http, profileId));
await Promise.all(apis) // await all promise done
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
}
dispatch({ type: FETCH_API_DATA, payload: payload }); // dispatch will be executed when all promise done
}

Related

How to add await inside Promise?

Is it possible to add Await inside new promise ?
Originally, I don't need to put a await before making any request to the server. But one day, the server requires every request to have a token before sending out.
Let's take an example of the code
export const countries = (data: IData) => {
const countryRequests = getRequests(data)
const countryResponse = countryRequests?.reduce((countryResponse, request) => {
const countryResponses = new Promise((resolve, reject) => {
instance
.post(`/country`, request)
.then(data => {
resolve(data)
})
.catch(err => {
reject(err)
})
})
return [...countryResponse, countryResponses]
}, [])
return countryResponse
}
new code( putting async into the callback of promise):
export const countries = (data: IData) => {
const countryRequests = getRequests(data)
const countryResponse = countryRequests?.reduce((countryResponse, request) => {
const countryResponses = new Promise(async (resolve, reject) => { //add async here
await addAccessToken() // add header into token before sending the requests
instance
.post(`/country`, request)
.then(data => {
resolve(data)
})
.catch(err => {
reject(err)
})
})
return [...countryResponse, countryResponses]
}, [])
return countryResponse
}
addToken function:
export const addAccessToken = async () => {
const accessToken = await instance.get<IAccessToken>(
'/access_token'
)
const { access_token } = accessToken.data
instance.defaults.headers.common['Authorization'] = `Be ${access_token}`
}
But then I got a error below
Promise executor functions should not be async.(no-async-promise-executor)
How can I get rid of the error?
-------------- new changes---------
export const countries = async (data: IData) => {
const countryRequests = getRequests(data)
await addAccessToken()
const countryResponse = countryRequests?.reduce((countryResponse, request) => {
const countryResponses = instance
.post(`/country`, request) //------- May I ask, if it is successful call, then this will autmactically equvlanet to calling resolve (data) in my previosu code?
.catch(err => {
console.error(err)
})
return [...countryResponse, countryResponses]
}, [])
return countryResponse
}
added new prmosie.all part
const countryResponses = countries(data)
//set content for api 1
Promise.all([...countryResponses])
.then(values => {
const countryResponsesResult = values.map((value, _index) => {
return value.data.result ? value.data.result : []
})
//Set content for api 1
props.setProjection({
kind: 'success',
payload: {
data: countryResponsesResult,
},
})
})
.catch(_error => {
//Set content for api 1
props.setProjection({
kind: 'fail',
payload: {
error: new Error(_error.message),
},
})
})
As #deceze mentioned, instance already returns a promise for you so no need to return your own promise.
Just
export const countries = async (data: IData) => {
await addAccessToken()
const countryResponses = await instance.post(`/country`, request)
//your code//
If you don't want to use await and use promises and then instead you should have something like the below(promise chaining):
export const countries = (data: IData) => {
addAccessToken()
.then((data)=>{
const countryResponses = instance.post(`/country`,
request)
})
.then(//your code//)
Somehow I found this work.
export const countries = (data: IData)=> {
const countryRequests = getRequests(data)
const countryResponse = countryRequests?.reduce((countryResponse, request) => {
// return promise
const countryResponses = new Promise((resolve, reject) => {
addAccessToken().then(()=>{
instance
.post(`/country`, request)
.then(data => {
// change the returned propmise state into resolved
resolve(data)
})
.catch(err => {
reject(err)
})
})
})
//return the whole set of simlationCalls promise. When all promise is resolved, promise all will be notified and excute whatever it needs to execute
return [...countryResponse, countryResponses]
}, [])
return countryResponse
}

Wait for a Javascript function to finish executing and fetch the response from it

I've a array of images and I am uploading these images to firebase storage.
data = {
...data,
downloadedUrl: [],
};
if (data?.image?.length) {
for (const image of data?.image) {
await uploadFile(image, data);
}
}
uploadFile handles the logic for uploading the image to firebase.
const uploadFile = useCallback((file, data) => {
if (!file) return;
const storageRef = ref(storage, `/images/${file.name}`);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
"state_changed",
(snap_shot) => {},
(err) => console.log(err),
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((url) => {
data.downloadedUrl.push(url);
});
}
);
}, []);
It takes few seconds to get the downloadedUrl from uploadTask and I want to store this downloadedUrl in firebase firestore when I get all the urls.
Issue with the current approach is that before getting the urls, the other function start executing and I am not able to upload this data on firestore with the downloadedUrl
Here's the full function when someone clicks on form submit
const handleFormSubmit = useCallback(
async (data) => {
setLoading(true);
data = {
...data,
downloadedUrl: [],
};
if (data?.image?.length) {
for (const image of data?.image) {
await uploadFile(image, data);
}
}
if (data.downloadedUrl.length) {
uploadDataToFirestore(data);
}
if (!data.downloadedUrl?.length) {
dispatch(handleAlertState({ message: "Error Occured!!!" }));
router.push("/services");
return;
}
setLoading(false);
router.push("/");
},
[dispatch, router, uploadDataToFirestore, uploadFile]
);
const uploadDataToFirestore = useCallback(
async (data) => {
await setDoc(doc(db, "form-responses"), data)
.then((response) => {
console.log("response", response);
dispatch(
handleAlertState({
message: "Success. Your request has been sent. Thank You.",
})
);
})
.catch((error) => {
console.log("error", error);
});
},
[dispatch]
);
This bellow block of code executes the code while images are being uploaded to the cloud storage.
I want to wait for the downloadedUrl and then upload the urls to firebase firestore.
if (!data.downloadedUrl?.length) {
dispatch(handleAlertState({ message: "Error Occured!!!" }));
router.push("/services");
return;
}
Create array of promises
Use Promise.all to watch for every promise
const uploadFile = useCallback((file, data) => {
return new Promise((resolve, reject) => {
if (!file) reject();
const storageRef = ref(storage, `/images/${file.name}`);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
'state_changed',
snap_shot => {},
err => reject(err),
() => resolve(getDownloadURL(uploadTask.snapshot.ref)),
);
});
}, []);
let allPromises = [];
if (data?.image?.length) {
for (const image of data?.image) {
allPromises.push(uploadFile(image, data));
}
}
let uploadedUrls = await Promise.all(allPromises);
console.log(uploadedUrls);
await Promise.all take an array of promises, we created a new array which holds the promise returned from uploadFile function. when all promises resolved then promise.all resolved as well and return array of urls. we await for Promise.all so it will not execute next line until resolved

Not receiving the correct data from Promises?

I have a function that fetches data from an API, and the function works correctly as intended:
const getStockData = async (stock) => {
try {
const response = await axios.get(`${BASE_URL}${stock}${KEY_URL}`);
console.log(response);
return response;
} catch (error) {
console.error('Error', error.message);
}
};
And I have another function that gets data from my firebase which then passes in the .ticker into the function above however when I log the response from the promise the data is returned null
Is there a reason why its not working as intended?
const getMyStocks = async () => {
let promises = [];
let tempData = [];
const querySnapshot = await getDocs(collection(db, 'myStocks'));
querySnapshot.forEach((doc) => {
console.log(doc.data().ticker);
promises.push(
getStockData(doc.data().ticker).then((res) => {
console.log(res);
tempData = {
id: doc.id,
data: doc.data(),
info: res.data,
};
})
);
getMyStocks must return the resolution of the promises it creates...
// to reduce nested promises, this takes an FB doc and adds the getStockData query to it
const getFBAndTickerData = async doc => {
return getStockData(doc.data().ticker).then(res => {
console.log(res);
return {
id: doc.id,
data: doc.data(),
info: res.data,
};
});
}
const getMyStocks = async () => {
const querySnapshot = await getDocs(collection(db, 'myStocks'));
let promises = querySnapshot.docs.map(doc => {
console.log(doc.data().ticker);
return getFBAndTickerData(doc);
});
return Promise.all(promises);
}

How to mock multiple promise .then in react hooks and check for state set in one .then

The question could be seen as similar to this one but is not working really the same way as that one is checking for a function to be called while im looking for a state to change.
The code i have is this one (headers and body are not really important in this case):
const useGetToken = () => {
const [token, setToken] = useState();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const fetchToken = useCallback(() => {
setLoading(true);
fetch('http://localhost.something', {
headers,
body,
})
.then((response) => {
response.json();
})
.then((response) => {
setToken(response.access_token);
})
.catch((e) => {
setError(e);
})
.finally(() => {
setLoading(false);
});
}, []);
return { fetchToken, token, error, loading };
};
what I am trying to find is a way to test that the output I have is the correct one in case of success and in case of error.
Seems like I can mock until the first .then but then i dont know how to mock the second one.
import { renderHook, act } from '#testing-library/react-hooks';
describe('useGetToken', () => {
it('should fetch and return a token', () => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => ({ access_token: 'aToken' }),
}),
);
const { result } = renderHook(() => useGetToken());
// also how to check for the fetchToken function to equal to itself i dont know how to do
// or maybe i can check if it is just a function
expect(result.current).toEqual({ token: 'aToken', loading: false, error: false });
});
});
managed a way to fix it changing with async await and the correct act.
also changed the double .then in the file to this
.then((response) => {
const parsedResponse = response.json();
setToken(parsedResponse.access_token);
})
cause i didnt need two
import { renderHook, act } from '#testing-library/react-hooks';
import useGetToken from '../useGetVfsToken';
describe('useGetToken', () => {
it('should fetch when fetchToken is called', async () => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => ({ access_token: 'aToken123' }),
}),
);
const { result } = renderHook(() => useGetToken());
expect(window.fetch).not.toHaveBeenCalled();
await act(async () => {
result.current.fetchToken();
});
expect(window.fetch).toHaveBeenCalledTimes(1);
expect(result.current.token).toEqual('aToken123');
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
});
it('should have an error', async () => {
const error = 'an error text';
global.fetch = jest.fn().mockImplementation(() => Promise.reject(error));
const { result } = renderHook(() => useGetToken());
await act(async () => {
result.current.fetchToken();
});
expect(result.current.error).toEqual(error);
expect(result.current.loading).toEqual(false);
});
});

how to properly use the async and await keywords within a map

I have the following snippet of code
export const fetchPosts = () => async dispatch => {
const res = await axios.get(`${url}/posts`, { headers: { ...headers } });
console.log(res.data);
let posts = res.data.map(p => (p.comments = fetchComments(p.id)));
console.log(posts);
dispatch({ type: FETCH_POSTS, payload: res.data });
};
export const fetchComments = id => async dispatch => {
console.log(id)
const res = await axios.get(`${url}/posts/${id}/comments'`, {
headers: { ...headers }
});
console.log("id", id);
return res.data;
};
when i console log the posts, i get 2 functions returned. what is the proper way in which i should call the fetch comments for this function to return me the desired value?
Add this:
const postsResult = await Promise.all(posts)

Categories

Resources