问题
Supose that I have the following for
loop, which would obviously block the event loop for a while:
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.';
}
}
}
Can I wrap that operation (the for
loop) inside a Promise, so it does not block the "main thread"?
Something like that:
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.');
}
}
});
}
P.S.: Numeric separators are now a thing in the JavaScript world.
回答1:
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 comes 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 communicate back results to the main thread via messaging.
回答2:
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 completely 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)
来源:https://stackoverflow.com/questions/58262465/create-a-promise-to-handle-a-really-long-loop