javascripttypescriptreact-nativereact-hooksreact-native-flatlist

Promise.race not resolving in useEffect function for React Native mobile app


I am setting up a progressive Image loader (download and replacing an image with the same image in increasing quality). First I am making a cache check to see if the image is already cached. However, while this loads, I am downloading lower quality images in case there is no cache hit of the image I want to load. The code looks like this:

useEffect(() => {
  // checks if image exists in cache, sets it to imageSource if it does
  async function checkCache(); 

  // downloads 2 lower quality images and sets them to imageSource when their download finishes
  async function downloadAndSetSmallAndMediumImages(); 

  // downloads the highest quality image and stores it in cache
  async function handleFinalBigImageDownloadAndSet();

  async function handleRace() {
    const raceResult = await Promise.race([checkCache(), downloadAndSetSmallAndMediumImages()]);
    console.log('here', raceResult);
    if (isCacheChecked && !cacheHit) {
      console.log('cache miss');
      await downloadAndSetSmallAndMediumImages();
      await handleFinalBigImageDownloadAndSet();
    else {
      console.log('cache hit');
    }
  }

  handleRace();
  return () => {};
}, [imageSource]);

In the handleRace() function, the console log never appears - it seems then that the Promise.race() is not resolving. I have set up some console.log() at the end of the checkCache() function, and these are actually logged in the console. The next line executing should then be the Promise.race() resolving and the subsequent logging of the result. Thus, on the UI of the app, the loading indicator stays indefinitely (since imageSource needs to be set by these functions) until I refresh the page and then suddenly the images are visible, yet the console.log() after the Promise.race() still does not show.


Solution

  • You need to work with the react lifecycle.

    The react useEffect do not handle async callbacks, so you should use promises without the await, just using the .then and the useSetate, for example:

    // outside the component context if it's possible
    async function checkCache(); 
    
    async function downloadAndSetSmallAndMediumImages(); 
    ...
      const [raceResult, setRaceResult] = useState(false):
      useEffect(() => {
      
        Promise.race([checkCache(), downloadAndSetSmallAndMediumImages()]).then(setRaceResult);
    
      }, [imageSource])
    
      useEffect(() => {
        console.log('here', raceResult);
        if (isCacheChecked && !cacheHit) {
          console.log('cache miss');
          // Use fire and forget if it's possible and apply the same approach with the useState in case of require to handle an action once the promise finish
          downloadAndSetSmallAndMediumImages().then(() => handleFinalBigImageDownloadAndSet());
        else {
          console.log('cache hit');
        }
      }, [raceResult, isCacheChecked, cacheHit])