嵌套的多层异步/等待似乎没有等待

时间:2020-02-22 15:24:35

标签: javascript promise async-await

我有一段代码的简化版,看起来像这样:

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
})();

如您所见,自执行伪伪main()调用await fetchData(),然后fetchData()调用await tryFetch(),最后tryFetch()调用await api.fetch('~')(在api中定义)。

但是,一旦启动脚本并在几次迭代后将其暂停,我注意到dataStorage1dataStorage2都保留为undefined。如果我在调试器中逐步执行代码,则会发生的情况是执行从fetchData()的开头开始,移至await tryFetch();行,然后跳过它,然后转到下一个迭代。

作为参考,如果我直接在dataStorage1/2 = await api.fetch(`~`);的主体中调用main()而没有任何嵌套,则它可以正常工作(除非发生错误,因为它们没有正确处理)。

所以,我的问题是我错过了什么?

2 个答案:

答案 0 :(得分:1)

我认为问题出在以下这一行:setTimeout(async () => await tryFetch(), delay);。回调中的await语句使该回调返回的承诺等待,而不是整个函数。因此async () => await tryFetch()是一个返回诺言的函数,但是没有什么等待该诺言完成。

尝试用某些行替换该代码

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

答案 1 :(得分:1)

实际上,如果在async函数中调用setTimeout,则不能指望它对与传递给await的回调相关的任何事情执行setTimeout。对setTimeout的调用会立即返回,并且您的while循环实际上是一个 synchronous 循环。这就是所谓的“忙循环”-阻塞您的GUI,因为它可能会循环数千次。

作为经验法则,仅使用setTimeout一次:来定义delay函数,然后再也不要使用。

也请避免使用诸如stopLight之类的全局变量:这是不正确的做法。让异步函数返回一个承诺,当它为true时,可以解决,否则返回拒绝

// 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
    }
})();