Вложенный многослойный async / await, похоже, не ждет - PullRequest
1 голос
/ 22 февраля 2020

У меня есть фрагмент кода, упрощенная версия которого выглядит следующим образом:

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.

Однако, как только я запустил скрипт и приостановил его после пары итераций, я заметил, что оба dataStorage1 и dataStorage2 остаются undefined. Если я go последовательно и последовательно выполняю код в отладчике, то происходит следующее: выполнение начинается в начале fetchData(), перемещается в строку await tryFetch();, пропускает ее и затем переходит к следующей итерации.

Для справки, если я вызываю dataStorage1/2 = await api.fetch(`~`); в теле main() напрямую, без вложенности, он работает отлично (если не возникает ошибка, поскольку они не обрабатываются должным образом).

Итак, мой вопрос, что я пропустил?

Ответы [ 2 ]

1 голос
/ 22 февраля 2020

Действительно, если в функции async вы вызываете setTimeout, вы не можете ожидать, что она выполнит await для всего, что относится к обратному вызову, переданному в setTimeout. Вызов setTimeout немедленно возвращается, и ваш while l oop фактически является синхронным l oop. Это так называемый «занятый l oop» - блокирующий ваш GUI, поскольку он потенциально будет l oop для тысяч раз.

Как правило, используйте setTimeout only один раз : определить функцию delay, а затем никогда больше.

Также избегайте использования глобальной переменной, такой как stopLight: это плохая практика. Пусть функция asyn c возвращает обещание, которое разрешает , когда предполагается, что это правда, и отклоняет , когда нет.

// 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
    }
})();
1 голос
/ 22 февраля 2020

Я думаю, что проблема в этой строке: setTimeout(async () => await tryFetch(), delay);. Оператор await внутри обратного вызова заставляет обещание, возвращаемое этим ожиданием обратного вызова, а не всю функцию. Так что async () => await tryFetch() - это функция, которая возвращает обещание, но ничего не ждет, пока это обещание будет выполнено.

Попробуйте заменить этот код строкой

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