简体   繁体   中英

Nested multilayered async/await doesn't seem to wait

I have a piece of code simplified version of which looks like this:

let dataStorage1; //declare global vars for easier access later on
let dataStorage2;
let stopLight = true; //this variable is used to 'mark' an iteration as successful (= true) or
//failed (= false) and in need of a retry before continuing to the next 
//iteration
let delay = 2000; //the standard time for a delay between api calls

async function tryFetch() {
  try {
    dataStorage1 = await api.fetch('data_type_1'); //fetch needed data trough api, which
    //fills the global variable with an 
    //object
    dataStorage2 = await api.fetch('data_type_2'); //do the same
    stopLight = true; //change the value of stopLight to true, thus marking this iteration
    //as successful
  } catch (err) {
    console.log(err);
    stopLight = false;
  }
}

async function fetchData() {
  stopLight = true; //change the stopLight to default before execution

  await tryFetch(); //fetch data and assign it to variables

  //this section is needed for retrial of fetching after a 2s delay if the first attempt was
  //unsuccessful, which is repeated until it's either successful or critical error occurred
  while (stopLight == false) {
    setTimeout(async () => await tryFetch(), delay);
  }
}

(async function main() {
  await fetchData(); //finally call the function
  setTimeout(main, delay); //repeat the main function after 2s
})();

As you can see, self-executing, pseudo-recursive main() calls for await fetchData() , then fetchData() calls for await tryFetch() and finally tryFetch() calls for await api.fetch('~') , as it's defined in the api.

However, once I started the script and paused it after a couple of iterations, I noticed that both dataStorage1 and dataStorage2 remain undefined . If I go through the code step by step in debugger, what happens is that the execution starts at the beginning of fetchData() , moves to the await tryFetch(); line, skips it, and then goes onto the next iteration.

For the reference, if I call dataStorage1/2 = await api.fetch(`~`); in the body of main() directly without any nesting, it works perfectly (unless error occurs, since they are not handled properly).

So, my question is what have I missed?

I think the problem is in this line: setTimeout(async () => await tryFetch(), delay); . The await statement inside the callback makes the promise returned by that callback wait, not the whole function. So async () => await tryFetch() is a function that returns a promise, but nothing waits for that promise to complete.

Try replacing that code with something line

await new Promise((resolve) => setTimeout(resolve, delay));
await tryFetch();

Indeed, if in an async function you call setTimeout you cannot expect it to perform an await on anything that relates to the callback passed to setTimeout . The call to setTimeout returns immediately, and your while loop is effectively a synchronous loop. It is a so called "busy loop" -- blocking your GUI as it potentially will loop for thousands of times.

As a rule of thumb, use setTimeout only once : to define a delay function, and then never again.

Also avoid using a global variable like stopLight : this is bad practice. Let the async function return a promise that resolves when this is supposed to be true, and rejects when not.

// Utility function: the only place to use setTimeout
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function tryFetch() {
    try {
        let dataStorage1 = await api.fetch('data_type_1'); 
        let dataStorage2 = await api.fetch('data_type_2'); 
        return { dataStorage1, dataStorage2 }; // use the resolution value to pass results
    } catch (err) {
        console.log(err);
        // retry
        throw err; // cascade the error!
    }
}

async function fetchData() {
    while (true) {
        try { 
            return await tryFetch(); // fetch data and return it
        } catch (err) {} // repeat loop
    }
}

(async function main() {
    let intervalTime = 2000; //the standard time for a delay between api calls

    while (true) { // for ever
        let { dataStorage1, dataStorage2 } = await fetchData();
        // ... any other logic that uses dataStorage1, dataStorage2
        //     should continue here...
        await delay(intervalTime); //repeat the main function after 2s
    }
})();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM