need to know when all async operation completed - reactjs

I have the following code:
callAPI() {
return this.myModel.map(action(retValue => {
return this.myApi.doWork(retValue.Input).then(action(model => {
this.model.Input = Object.assign({}, model);
this.saveState();
})).catch(error => {
throw(error);
});
}));
if my client code I am doing something like this:
myStore.callAPI().then(() => {
console.log("completed");
this.setState({ loading: false });
});
I am getting an error
.then() is not a function
I want to get a callback when all async operations are completed.

Please use Promise.all to await multiple promises and return the result which is another Promise which you can call .then on.
In order to always return a promise after an imperative block of code you can return Promise.resolve() that will make the code chainable.
callAPI() {
return Promise.all(
this.myModel.map(action(retValue => (
this.myApi.doWork(retValue.Input).then(action(model => {
this.model.Input = Object.assign({}, model);
this.saveState();
return Promise.resolve();
}))
)));
);
}
Please see an example:
const sleep = timeout => new Promise(resolve =>
setTimeout(() => resolve(timeout), timeout)
);
const tap = timeout => {
console.log(`Task completed after ${timeout}ms`);
return timeout;
}
const tasks = [
sleep(1000),
sleep(2000),
sleep(500),
sleep(30),
sleep(2500),
sleep(450)
].map(task => task.then(tap));
Promise.all(tasks).then(x => {
console.log(x);
});

Related

React how to wait for all axios to finish

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))
}, [])

Test that errors are thrown in use Effect hook

I have a component that fetches data wrapped in a function to made async calls cancelable:
useEffect(() => {
const asyncRequest = makeCancelable(myService.asyncRequest());
asyncRequest.promise
.then((result) =>
setState(result),
)
.catch((e) => {
if (!e?.isCanceled) {
//Case the rejection is not caused by a cancel request
throw e;
}
});
return () => {
asyncRequest.cancel();
};
},[])
I want to test that, when the rejection is not coming from a cancel request, the error is re-thrown (I'm filtering out cancel rejections since they are not true errors). So the goal is intercept exceptions coming from useEffect
How can I test it with enzyme and/or jest?
it('should not filter rejection not caused by cancel', () => {
let promise = Promise.reject(new Error('Generic error'));
when(myService.asyncRequest()).thenReturn(promise); // This will cause useEffect to throw
const myComponent = mount(<MyComponent />) // How to intercept the error?
})
To give further context here is the code of makeCancelable:
export function makeCancelable<T>(promise: Promise<T>): CancelablePromise<T> {
let isCanceled = false;
const wrappedPromise = new Promise<T>((resolve, reject) => {
promise.then(
(val) => (isCanceled ? reject({ isCanceled: true }) : resolve(val)),
(error) => (isCanceled ? reject({ isCanceled: true }) : reject(error)),
);
});
return {
promise: wrappedPromise,
cancel() {
isCanceled = true;
},
};
}

Clean up async function in an useEffect React hook

I have the following useEffect function and trying to find the best way to clean this up when the component unmounts.
I thought it would be best to follow the makeCancelable from the React docs, however, the code still executes when the promise is cancelled.
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => hasCanceled_ ? reject({isCanceled: true}) : resolve(val),
error => hasCanceled_ ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
//example useEffect
useEffect(() => {
const getData = async () => {
const collectionRef_1 = await firestore.collection(...)
const collectionRef_2 = await firestore.collection(...)
if (collectionRef_1.exists) {
//update local state
//this still runs!
}
if (collectionRef_2.exists) {
//update local state
//and do does this!
}
}
const getDataPromise = makeCancelable(new Promise(getData))
getDataPromise.promise.then(() => setDataLoaded(true))
return () => getDataPromise.cancel()
}, [dataLoaded, firestore])
I have also tried const getDataPromise = makeCancelable(getData) without any luck. The code executes fine, just doesn't clean up correctly when the component unmounts.
Do I need to also cancel the two await functions?
In your makeCancelable function you are just checking the value of hasCanceled_ after the promise has finished (meaning getData has already executed entirely):
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
// AFTER PROMISE RESOLVES (see following '.then()'!), check if the
// react element has unmount (meaning the cancel function was called).
// If so, just reject it
promise.then(
val => hasCanceled_ ? reject({isCanceled: true}) : resolve(val),
error => hasCanceled_ ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
Instead, in this case I would recomend you to go for a simpler and more classic solution and use a isMounted variable to create the logic you want:
useEffect(() => {
let isMounted = true
const getData = async () => {
const collectionRef_1 = await firestore.collection(...)
const collectionRef_2 = await firestore.collection(...)
if (collectionRef_1.exists && isMounted) {
// this should not run if not mounted
}
if (collectionRef_2.exists && isMounted) {
// this should not run if not mounted
}
}
getData().then(() => setDataLoaded(true))
return () => {
isMounted = false
}
}, [dataLoaded, firestore])

using axios with promise API

I am using a promise based hook in a React app to fetch async data from an API.
I am also using a Axios, a promise based http client to call the API.
Is it an anti-pattern to use a promise based client inside another promise? The below code does not seem to work.
const getData = () => {
return new Promise((resolve, reject) => {
const url = "/getData";
axios.get(url)
.then(function(response) {
resolve(response);
})
.catch(function(error) {
reject(error);
});
});
const useAsync = (asyncFunction) => {
const [value, setValue] = useState(null);
const execute = useCallback(() => {
setPending(true);
setValue(null);
setError(null);
return asyncFunction()
.then(response => setValue(response))
.catch(error => setError(error))
.finally(() => setPending(false));
}, [asyncFunction]);
useEffect(() => {
execute();
}, [execute]);
return { execute, pending, value, error };
};
};
const RidesList = () => {
const {
pending,
value,
error,
} = useAsync(getData);
Oh man. I think you have a fundamental misunderstanding about how Promises work.
First, axios already returns a Promise by default. So your whole first function of getData can be reduced to:
const getData = () => {
const url = "/getData"
return axios.get(url)
}
But the meat of your code seems to indicate you want a querable Promise - so you can check the status of it for whatever reason. Here's an example of how you would do it, adapted from this snippet:
function statusPromiseMaker(promise) {
if (promise.isResolved) return promise
let status = {
pending: true,
rejected: false,
fulfilled: false
}
let result = promise.then(
resolvedValue => {
status.fulfilled = true
return resolvedValue
},
rejectedError => {
status.rejected = true
throw rejectedError
}
)
.finally(() => {
status.pending = false
})
result.status = () => status
return result
}
In this way, you can then do something like let thing = statusPromiseMaker(getData()) and if you look up thing.status.pending you'll get true or false etc...
I didn't actually run what's above, I may have forgotten a bracket or two, but hopefully this helps.
I have to admit - I haven't seen anything like this ever used in the wild. I am interested in knowing what you're actually trying to accomplish by this.
Axios itself returns a promise but if you want to make a custom class having your custom logic after each API call then you can use interceptors I was having the same requirement and this is how I am returning promises after applying my custom logic on each API call.
Interceptors will get executed separately after and before each request you made so we can simply use them if we want to modify our request or response.
here is my working solution have a look at it.
callApi = (method, endpoint, params) => {
this.apiHandler.interceptors.request.use((config) => {
config.method = method
config.url = config.baseURL + endpoint
config.params = params
return config
})
return new Promise((resolve, reject) => {
this.apiHandler.interceptors.response.use((config) => {
if (config.status == 200) {
resolve(config.data)
} else {
reject(config.status)
}
// return config
}, error => reject(error))
this.apiHandler()
})
}
Below is the code to call this function
helper.callApi("get", "wo/getAllWorkOrders").then(d => {
console.log(d)
})

How to cover Promise.all(...) statement with unit test

I cannot write a test that covers Promise.all() statement within a asynchronous function (loadMessages()) that is ran in setTimeout() block of componentDidMount method.
In componentDidMount there is this.loadMessages() function that is called within setTimeout callback, in order for me to complete my test i need loadMessages() executed.
componentDidMount() {
const { conversationId } = this.state
const POLLING_INTERVAL = 3000
if (conversationId) {
setTimeout(() => this.loadMessages(), 0)
this.timer = setInterval(() => this.loadMessages(), POLLING_INTERVAL)
} else {
this.setState({ loading: false })
}
}
I resolved setTimeout callback with
await new Promise(resolve =>
setTimeout(() => {
resolve()
}, 3000)
)
and that solves a function call, but when start executing a function the report coverage is saying that Promise.all is not covered and function itself looks like:
async loadMessages() {
const { messages, conversationId, errors } = this.state
let messagesWithAuthors
// initial load
if (messages.length === 0) {
try {
let initialMessages = await runtime.dais.communication.auto.getMessagesByConversationId(
conversationId
)
const messageAuthors = await Promise.all(
initialMessages.map(async message =>
//runtime.dais.orgService.auto.getPersonById(message.sender.id)
runtime.dais.organization.auto.getPersonById(message.sender.id)
)
)
messagesWithAuthors = initialMessages.map((message, i) => ({
...message,
author: messageAuthors[i],
}))
this.setState({
messages: messagesWithAuthors,
messageAuthors,
loading: false,
})
} catch (error) {
this.setState({ errors: [...errors, error], hasErrors: true, modalOpen: true })
}
} else {
let updatedMessages = await runtime.dais.communication.auto.getMessagesByConversationId(
conversationId
)
this.checkIfNeedUpdate(updatedMessages)
}
}
is there some way to mock a values that are returned from Promise.all() into messageAuthors variable?
I am testing using #testing-library/react and my test looks like this
it('ensure that we have chat window shown if we have conversation as a prop', async () => {
const queries = render(
<CommWidget runtime={runtime} conversationId="fe3d52fc-ffb3-482a-aedf-79000645ca70" />
)
await new Promise(resolve =>
setTimeout(() => {
resolve()
}, 3000)
)
const commWidget = queries.container.querySelector(
'.ui-comm-widget .ui.segments.comm-widget #chat-window'
)
expect(commWidget).toBeInstanceOf(HTMLDivElement)
})
Please don't put a timeout in your tests that's an anti-pattern. What happens after the promise resolves? Is the page going to change? If so wait for the change to appear. See this article for an introduction on testing async methods.

Resources