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
2 Answers
Reset to default 6Only 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.