I'm trying to incrementally fetch episodes from an array, so the user experience would look like this:
A user clicks a button, shows a loading indicator, loads the first episode fetched inside a div, but keeps showing the loading indicator below that first episode, keeps doing this until the map is over and all the episodes are fetched and the loading indicator is not shown anymore.
But apparently, this does not appear to be working as planned, I'm suspecting that async-await does not work as expected inside a map.
I'm wondering if something like rxjs
would help in this use case(I have no idea how it works, but I know it is used in cases where the data is streaming)
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [data, setData] = useState([]);
const handleClick = async () => {
try {
season.episodes.map(async (episode) => {
setIsLoading(true);
const links = await publicQueryClientApi(
`${process.env.NEXT_PUBLIC_SERVER_URL}/download/${episode.slug}`
);
setIsLoading(false);
setData((prev) => [...prev, { episode, links }]);
setError('');
});
} catch (error) {
setIsLoading(false);
setData([]);
setError(error.message);
}
};
CodePudding user response:
async
function always returns a Promise
.
You can try something like this:
const promises = season.episodes.map(async episode => {
const links = await fetchLinks()
// do other stuff
return links
})
const links = await Promise.all(promises)
And the other thing is, you only use map
when you want a copy of the array but modified (using a function you pass to map
that will be executed on every array item). From what I can see your return value from map
function is not saved anywhere.
Why not just use recursive function, that will fetch each episode, and call itself again to fetch another for as long as there are "unfetched" episodes? (Don't forget to mark episode as "fetched" each time, otherwise you will make an infinite loop).
CodePudding user response:
array.map
does not await
its callback, if it's async. So the result of
[...].map(async x => {})
is an array of pending promises. If you want to add your episode
one by one, you should use a
try {
for (let episode of season.episodes) {
setIsLoading(true);
const links = await publicQueryClientApi(
`${process.env.NEXT_PUBLIC_SERVER_URL}/download/${episode.slug}`
);
setIsLoading(false);
setData((prev) => [...prev, { episode, links }]);
setError('');
}
} catch (e) {
...
}
As your code is already inside a async
function, this should work without any further modifications.
Another possibility is to use Promise.all()
, this will resolve once all of the promises in the array are resolved and return their results in an array
try {
setIsLoading(true);
let allLinks = await Promise.all(season.episodes.map(e => {
return publicQueryClientApi(...);
});
setIsLoading(false);
setData(season.episodes.map((e,i) => ({
episode: e,
links: alllinks[i]
}));
setError('');
} catch (e) {
...
}