Javascript — правильный способ ожидания сверхдлинного цикла?

#javascript

#javascript

Вопрос:

Может быть, это ситуация с асинхронностью / ожиданием, но я думал, что асинхронность предназначена в основном для вызовов api (?)

Как дождаться завершения длинного цикла, например:

 let x;
for (i = 1, i < 10000000000000000, i  ) {
  x  = (i * 99999);
}

console.log(x);
  

Комментарии:

1. Здесь нет асинхронной операции. Цикл выполняется синхронно, поэтому вы «ждете», но просто не можете ничего сделать в той же среде выполнения, пока это не закончится. Обратите внимание, что все выполнение должно будет подождать этого, больше ничего не сработает.

2. «Как вы ждете завершения длинного цикла» Я обычно пью кофе

3. @BenCoupe ответ: да

4. @Cid это немного вводит в заблуждение, поскольку асинхронные функции обрабатываются синхронно до тех пор, пока не встретится либо an await , либо a return . В этот момент они откладывают исполнение и дают обещание. So async function() { doHeavySynchronousOperation() } заблокирует поток до doHeavySynchronousOperation завершения, и только тогда вы получите обещание, которое разрешит undefined .

5. Предполагая, что пример сильно упрощен (полученное число намного превышает максимальное число, которое может представить JS), и у вас действительно трудоемкая задача, делегируйте ее веб-работнику.

Ответ №1:

Я думал, что асинхронность предназначена в основном для вызовов api (?)

Есть два разных понятия, связанных со async словом:

  • async/await синтаксический сахар функций для получения результатов обещаний синхронным образом
  • реальное асинхронное поведение Вызовы API XHR, отложенное выполнение, обработка событий

async/await функция не делает выполнение вашей функции асинхронным автоматически.

 const foo = async (i) => { console.log('foo running', i); return i == 0 ? 42 : await foo(i-1); };
console.log('foo before')
foo(5)
  .then(x => console.log('foo', x))
console.log('foo after')

// foo before
// foo running 5
// foo running 4
// foo running 3
// foo running 2
// foo running 1
// foo running 0
// foo after
// foo 42  

Javascript является однопоточным, все параллельные задачи должны быть разделены на асинхронные блоки, чтобы иметь возможность работать другим.

Итак, вы должны разделить цикл синхронизации на множество асинхронных частей, чтобы не зависнуть.

Например (я уменьшаю параметры, чтобы не было слишком много времени для ожидания):

 async function calcX() {
  let x = 0;
  function iteration(i) {
    x  = (i * 99999);
    if (  i >= 10000) return Promise.resolve(x);
    return new Promise((resolve) => {
      setTimeout(() => iteration(i).then(resolve), 0);
      // or requestAnimationFrame
    });
  }
  return await iteration(1);
}
const start = Date.now();
calcX()
  .then(x => console.log(x, Date.now() - start), err => console.error(err));

// 4999450005000 42465  

Это может быть слишком медленно, если вы помещаете каждую итерацию в цикл событий. Таким образом, вы можете оптимизировать его, объединив их (см. Ответ @Steve’s)

Или используйте WebWorker для выполнения тяжелой задачи синхронизации

Комментарии:

1. » Определенно, нет. Каждый setTimeout или addEventListener в вашем коде также является асинхронным «. но не совсем так, как асинхронная функция, и эти два не имеют ничего общего с promises . Таким образом, пока они асинхронны, это своего рода концепция, отличная от той, которую вы будете использовать async в коде.

Ответ №2:

Вы можете преобразовать длительно работающие синхронные функции в асинхронные функции, проверив, прошло ли заданное количество времени, а затем вернуться к функции позже (достигается в этом примере с помощью setTimeout ):

 var lastBreak = Date.now()

function takeABreak() {
    return new Promise(resolve=>setTimeout(resolve));
}

async function maybeTakeABreak() {
    if (Date.now() - 17 > lastBreak) {
        lastBreak = Date.now();
        await takeABreak();
    }
}

async function myLongLoop() {
    let x = 0;
    for (let i = 1; i < 100000000000000; i  ) {
       await maybeTakeABreak();
       x  = (i * 99999);
       if (!(i%1000000)) {
          console.log(i);
          // alternatively you could run `await takeABreak();` here
       }
    }
    return x;
}
myLongLoop().then(x=>console.log(x));