最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Can promises be used to handle really long and blocking loops in JavaScript? - Stack Overflow

programmeradmin2浏览0评论

Supose that I have the following for loop, which would obviously block the event loop for a while:

function somethingExpensive() {
  let i = 0;
  while (true) {
    // Something expensive...
    if (++i === 1e10) return 'Some value.'
  }
}

Can I wrap that operation (the for loop) inside a Promise, so it does not block the "main thread"?

Something like that:

function somethingExpensive() {
  return new Promise((resolve) => {
    let i = 0;
    while (true) {
      // Something expensive...
      if (++i === 1e10) resolve('Some value.');
    }
  });
}

Supose that I have the following for loop, which would obviously block the event loop for a while:

function somethingExpensive() {
  let i = 0;
  while (true) {
    // Something expensive...
    if (++i === 1e10) return 'Some value.'
  }
}

Can I wrap that operation (the for loop) inside a Promise, so it does not block the "main thread"?

Something like that:

function somethingExpensive() {
  return new Promise((resolve) => {
    let i = 0;
    while (true) {
      // Something expensive...
      if (++i === 1e10) resolve('Some value.');
    }
  });
}
Share Improve this question edited May 17, 2021 at 0:10 Luiz Felipe asked Oct 7, 2019 at 1:08 Luiz FelipeLuiz Felipe 9971 gold badge13 silver badges23 bronze badges 3
  • 5 Javascript is by design single-threaded. Promises promise future execution at some point, but they still block the main thread. You can get around this, though. In node, use multi-threading. On the web, use service workers – Ben Aubin Commented Oct 7, 2019 at 1:10
  • 1 No, because once the code in the Promise executor starts, it will continue until it ends - and the Promise executor is executed synchronously anyway, so there's no asynchrony at all in that solution – Bravo Commented Oct 7, 2019 at 1:11
  • Unless you use asynchronous workers, anything that takes a long time will block the event loop. The main event loop queues all event handlers so you shouldn't run anything that takes more than a few milliseconds to plete or your page will feel laggy or unresponsive. – Jochem Kuijpers Commented Oct 7, 2019 at 1:12
Add a ment  | 

2 Answers 2

Reset to default 6

Only to a limited extent. If the expensive operation occurs in the same Javascript environment, the environment's resources will be taken up by the expensive operation, no matter whether the expensive operation occurs synchronously or asynchronously (like after a Promise.resolve). For example, the user may be unable to interact with the page while the operation is ongoing.

Use a service worker instead, so that the expensive operations take place in a pletely separate environment, allowing the original page to be interacted with as normal. For example:

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>

The worker there will consume a LOT of resources, but the original window will remain scrollable and interactable, as desired. Without a worker, you'd either have to live with the original window being unresponsive while the expensive operation is going on, or stagger out the expensive operation into multiple inexpensive chunks that are called, for example, every 100ms. (though, even using that method, the window may not be be as responsive as one would like - using a worker is better)

No, you can't use promises alone to allow all other events to run in the event loop. That's because native promises in ES6+ are high priority events and will get their turn before other types of events so those other types of events can still get starved.

The usual way to allow all other events to interleave with your loop is to use a short setTimeout() at some point to control the iteration of the loop so that there's legitimately a delay between iterations of the loop. The setTimeout() with at least a short delay allows all other types of events to get a turn.

One way to do this with a regular for loop is like this (which uses a promise just to internally pause the for loop, but the real event loop help es from the setTimeout() in the delay() function):

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
    }
}    

If you really want to separate out your time consuming loop from event loop processing, then it's best to get it out of the same event loop at all. In node.js you can use Worker threads and in the browser you can use WebWorkers. These can then municate back results to the main thread via messaging.

发布评论

评论列表(0)

  1. 暂无评论