Создайте обещание обработать действительно длинный цикл - PullRequest
4 голосов
/ 07 октября 2019

Предположим, у меня есть следующий цикл for, который, очевидно, на некоторое время заблокирует цикл событий:

function doStuff() {
  let max = 100_000_000_000_000n;

  for (let i = 1n; i <= max; i = i + 1n) {
    if (i === max) {
      // Do stuff, like return a value to a function.
      return 'Some value.';
    } 
  }
}

Можно ли обернуть эту операцию (цикл for) внутри Обещания,чтобы он не блокировал «главный поток»?

Что-то в этом роде:

function doStuff() {
  let max = 100_000_000_000_000n;

  return new Promise((resolve) => {
    for (let i = 1n; i <= max; i = i + 1n) {
      if (i === max) {
        // Do stuff, like return a value to a function.
        resolve('Some value.');
      } 
    }
  });
}

PS: Числовые разделители теперь вещьв мире JavaScript.

Ответы [ 2 ]

4 голосов
/ 07 октября 2019

Только в ограниченной степени. Если дорогая операция происходит в той же среде Javascript, ресурсы этой среды будут использованы дорогой операцией, независимо от того, выполняется ли дорогая операция синхронно или асинхронно (например, после Promise.resolve). Например, пользователь может не иметь возможности взаимодействовать со страницей во время выполнения операции.

Вместо этого используйте сервисного работника, чтобы дорогостоящие операции выполнялись в совершенно отдельной среде, позволяя исходной странице бытьвзаимодействовал как обычно. Например:

const workerFn = () => {
  // something expensive
  while (true) {
  }
};
const workerFnStr = `(${workerFn})();`;
const blob = new Blob([workerFnStr], { type: 'text/javascript' });
const worker = new Worker(window.URL.createObjectURL(blob));

button.onclick = () => console.log('clicked');
body {
  height: 1000px;
}
<button id="button">click</button>

Рабочий там будет потреблять МНОГО ресурсов, но исходное окно останется прокручиваемым и интерактивным по желанию. Без работника вам пришлось бы либо жить с неподходящим исходным окном во время выполнения дорогостоящей операции, либо разбивать дорогостоящую операцию на несколько недорогих блоков, которые называются, например, каждые 100 мс. (хотя, даже используя этот метод, окно может быть не таким отзывчивым, как хотелось бы - лучше использовать рабочий)

3 голосов
/ 07 октября 2019

Нет, нельзя использовать одни только обещания, чтобы все остальные события могли выполняться в цикле событий. Это связано с тем, что собственные обещания в ES6 + являются событиями с высоким приоритетом, и их ход наступает раньше других типов событий, поэтому эти другие типы событий все еще могут голодать.

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

Один из способов сделать это с помощью обычного цикла for такой: он использует обещание только для внутреннего использования. Приостановите цикл for, но справка по реальному циклу событий поступает из setTimeout() в функции delay()):

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


async function doStuff() {
  let max = 100_000_000_000_000n;

    let cntr = 1;
    for (let i = 1n; i <= max; i = i + 1n) {
        // let other stuff run once in every 100 iterations of the loop
        if (cntr % 100 === 0) {
            // give other things in the event queue a chance to run 
            // note: this use setTimeout(), not just promises
            await delay(10);  
        }
        // do the work for your loop here
    }
}    

Если вы действительно хотите отделить цикл, отнимающий много временииз обработки цикла событий, то лучше вообще вывести его из одного цикла событий. В node.js вы можете использовать рабочие потоки, а в браузере - WebWorkers. Затем они могут передавать результаты в основной поток через обмен сообщениями.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...