-1

I have used await keyword in the main function to wait for the completion of async function call to poll() and yet the function call to my_plot is made before the completion of the poll() function.

async function main() {
    getParametersData()
    await poll()
    my_plot()
} 
async function getData() {
    const response = await fetch(API)
    const message = await response.json()
    return message
}

async function poll(count = 1) {
    console.log(`Polling ${count}`);
    try {
        const data = await getData();
        if (data && Object.keys(data).length !== 0) {
            console.log("Poll", data)
            return;
        } else {
            setTimeout(poll, 5000, ++count);
        }
    } 
    catch (err) {
        console.log(`${err}. Polling again in 5 seconds.`);
        setTimeout(poll, 5000, 1);
    }

}

async function my_plot() {
    console.log("my plot")
}

Code output:

Polling 1
my plot 
Polling 2
Polling 3
Poll [1,2,3]

Expected:

Polling 1
Polling 2
Polling 3
Poll [1,2,3]
my plot
Amogh Joshi
  • 449
  • 5
  • 13
  • What is `getData()`? – Dai Dec 28 '21 at 04:47
  • @Dai updated the code – Amogh Joshi Dec 28 '21 at 04:48
  • 1
    `setTimeout` doesn’t operate the way you think it does. It causes the subsequent/recursive invocations of your `poll` method to be executed asynchronously, which is why you’re seeing this behavior. This is pretty clearly documented on the [associated MDN page](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#working_with_asynchronous_functions). – esqew Dec 28 '21 at 04:49
  • `setTimeout` will launch a new task, which you can't await directly. You would need to create a promise and resolve it inside the timeout callback in order for it to behave like you want to. See [How to make a promise from setTimeout](https://stackoverflow.com/questions/22707475/how-to-make-a-promise-from-settimeout). – Amadan Dec 28 '21 at 04:50
  • @esqew is there any other way to accomplish what I am trying to do? Any help or suggestions would be appreciated – Amogh Joshi Dec 28 '21 at 04:50
  • @AmoghJoshi There sure is, have you considered the advice Amadan provided in their comment above? – esqew Dec 28 '21 at 04:52
  • @Amadan what kind of Promise should I add for this? I am a newbie to JS and am unable to grasp the concepts – Amogh Joshi Dec 28 '21 at 04:52
  • @AmoghJoshi You'll need to rewrite your entire poll loop _with_ `setTimeout` inside a manually-managed `Promise`. – Dai Dec 28 '21 at 04:52

2 Answers2

2

Don't use setTimeout directly from within an async function. Instead, use a Promise-based wrapper.

It's surprising that modern ECMAScript doesn't come with an in-box Promise-based version of setTimeout, but it's straightforward to implement:

function delay( timeout ) {
    if( typeof timeout !== 'number' || timeout < 0 ) throw new Error( "Timeout must be a non-negative integer milliseconds delay value." );

   return new Promise( function( resolve ) { 
       setTimeout( resolve, timeout );
   });
}
  • Then you can rewrite your poll function with a "real" while loop, like so (below).
  • I think your poll function should return a true/false value to indicate success or failure to the caller, if you ever need to.
  • Consider using typeof instead of less safe checks like Object.keys(data).length - or at least using a typeof check before using Object.keys.
    • Though annoyingly typeof null === 'object', so you will always need a !== null check, grumble...
    • As an alternative, consider having your own type-guard function (yes, I know this isn't TypeScript), that way you get even stronger guarantees that data contains what you need (as JS does not have static type checking).
async function poll( count = 1 ) {
    
    console.log(`Polling ${count}`);
 
    let i = 0;
    do {
        try {
            const data = await getData();
            if( isMyData( data ) ) {
                return true;
            }
        }
        catch( err ) {
            console.error( err );
        }

        console.log( "Polling again in 5 seconds." );
        await delay( 5000 );

        i++;
    }
    while( i < count );

    console.log( `Gave up after ${count} attempts.` );
    return false;
}

// Type-guard:
function isMyData( data ) {
    
    return (
        ( typeof data === 'object' )
        &&
        ( data !== null )
        &&
        ( 'this is my object' in data )
        &&
        ( data['there are many like it but this one is mine'] )
        &&
        ( data.myJavaScriptEngineIsMyBestFriend )
        &&
        data.itIsMyLife
        &&
        data.withoutMe_javaScriptIsUseless
        &&
        data.withoutJavaScript_iAmUseLess > 0
    );
}

Note that if you intend to catch errors thrown by getData you should use a minimally scoped try instead of having more logic in there, as generally you won't want to catch unrelated errors.

Dai
  • 141,631
  • 28
  • 261
  • 374
  • @SebastianSimon - I've improved it – Dai Dec 28 '21 at 05:17
  • _“It's surprising that modern ECMAScript doesn't come with an in-box `Promise`-based version of `setTimeout`”_ — See [Bringing setTimeout to ECMAScript](//esdiscuss.org/topic/bringing-settimeout-to-ecmascript), and [Promise-returning delay function](//esdiscuss.org/topic/promise-returning-delay-function). – Sebastian Simon Dec 28 '21 at 05:17
  • @SebastianSimon Those threads are over a decade old, _erk_ – Dai Dec 28 '21 at 05:19
0

Using the answer from How to make a promise from setTimeout, you can use a traditional loop.

function later(delay, value) {
  return new Promise(resolve => setTimeout(resolve, delay, value));
}

async function poll() {
  for (let count = 1;; count++) {
    console.log(`Polling ${count}`);
    try {
      const data = Math.random(); // simulated data
      if (data < 0.2) { // simulated 20% success rate
        console.log("Poll", data)
        return data;
      } else {
        console.log("Retrying in 5 seconds");
        await later(5000);
      }
    } catch (err) {
      console.log(`${err}. Polling again in 5 seconds.`);
      count = 1;
      await later(5000);
    }
  }
}

async function main() {
  console.log("Start");
  await poll();
  console.log("Poll done");
}

main();
Amadan
  • 191,408
  • 23
  • 240
  • 301