skip to Main Content

I am calling a smart contract function to get some status which is the same as calling an API. And, I need to check if status.fulfilled===true before returning it to front-end. To do this I need to call the API every second to return the result as soon as possible. It usually takes 5-20 seconds for it to be fulfilled.

Here is how i tried to do it:

   async function getStatus(requestId) {
    try {
      await Moralis.enableWeb3({ provider: 'metamask' });
      const options = {
        contractAddress: coinFlipAddress,
        functionName: 'getStatus',
        abi: coinFlipABI,
        params: { requestId },
      };
      var status = await Moralis.executeFunction(options);
      console.log(status);
      if (status.fulfilled) {
        console.log('fulfilled');
        return status;
      } else {
        setTimeout(async () => {
          return await getStatus(requestId);
        }, 1000);
      }
    } catch (err) {
      console.log(err);
      return { error: err };
    }
  }

This keeps calling the getStatus function recursively until status.fulfilled===trueand console.log('fulfilled'); also logs when it is fulfilled, but it doesn’t return it to where It is first initialized.

  const handleFlip = async (choice) => {
    setCurrentChoice(null);
    setMetamaskInProgress(true);
    const transaction = await flip(choice, amount);
    setMetamaskInProgress(false);
    setCurrentChoice(choices[choice]);
    setFlipping(true);
    setResult('Spinning');
    const requestId = waitForConfirmation(transaction);
    const result = await getStatus(requestId); //This is the initial call to getStatus()
    console.log('RESULT ' + result);
    if (result) {
      setFlipping(false);
      setSide(result.hasWon ? (choice === '0' ? 'Heads' : 'Tails') : choice === '0' ? 'Tails' : 'Heads');
      setResult(result.hasWon ? 'You have won!' : 'You have lost :(');
    }
  };

What am I doing wrong? Also, could this recursive calls create any problems with memory? If yes, do you have any suggestions to handle this case differently?

3

Answers


  1. You could try this change maxRetry and spaceBetweenRetry as you wish

      async function getStatus(requestId) {
        return new Promise(async (resolve, reject) => {
        try {
          let maxRetry = 10; //how many times you want to retry
          let spaceBetweenRetry = 1000; // sleep between retries in ms
          await Moralis.enableWeb3({ provider: 'metamask' }); //Guessing you need to call this only once
          const options = {
            contractAddress: coinFlipAddress,
            functionName: 'getStatus',
            abi: coinFlipABI,
            params: { requestId },
          };
          for (let index = 0; index < maxRetry; index++) {
            var status = await Moralis.executeFunction(options); // check status
            console.log(status);
            if (status.fulfilled) {
              resolve(status) //return the promise if fullfilled.
            }
            await new Promise((r) => setTimeout(r, spaceBetweenRetry)); // sleep for spaceBetweenRetry ms
          }
        } catch (err) {
          reject(err)
        }
      })}
    
    Login or Signup to reply.
  2. You cannot return from a setTimeout callback. You’ll need to promisify that, wait, and return afterwards:

    async function getStatus(requestId) {
      try {
        await Moralis.enableWeb3({ provider: 'metamask' });
        const options = {
          contractAddress: coinFlipAddress,
          functionName: 'getStatus',
          abi: coinFlipABI,
          params: { requestId },
        };
        var status = await Moralis.executeFunction(options);
        console.log(status);
        if (status.fulfilled) {
          console.log('fulfilled');
          return status;
        } else {
          await new Promise(resolve => {
            setTimeout(resolve, 1000);
          });
          return getStatus(requestId);
        }
      } catch (err) {
        console.log(err);
        return { error: err };
      }
    }
    
    Login or Signup to reply.
  3. I would have done something like this :

    const MAX_RETRIES = 10; //maximum retries
    const REQUEST_DELAY = 1000; //delay between requests (milliseconds)
    
    // JS Implementation of the wide known `sleep` function
    const sleep = (time) => new Promise(res => setTimeout(res, time, "done."));
    
    /**
     * Retrieve the status
     * @param {string} requestId 
     */
    const getStatus = async (requestId) => {
        try {
          await Moralis.enableWeb3({ provider: 'metamask' }); //Guessing you need to call this only once
          const options = {
            contractAddress: coinFlipAddress,
            functionName: 'getStatus',
            abi: coinFlipABI,
            params: { requestId },
          };
    
          let retries = 0;
          while(retries < MAX_RETRIES) {
            let status = await Moralis.executeFunction(options); // check status
            console.log('attemtp %d | status %s', retries, status);
            if (status.fulfilled) {
              return status 
            }
            await sleep(REQUEST_DELAY);
            retries++;
          }
          throw new Error('Unable to retrieve status in time');
        } catch (error) {
          console.error('Error while fetching status', error);
          throw error;
        }
    }
    

    Few notes here :

    I took the constants out of the function for more clarity,
    use the while to loop for the number of retries.

    Used a widely known ‘sleep’ method to create a delay between requests, and throw an error whenever something’s not supposed to happen, happens (up to you to edit according to your needs).

    Finally I used arrow functions for simplicity of use, know it’s up to you 😉

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search