Javascript Background Loop

Say we have a loop.js file:

 longLoop().then(res => console.log('loop result processing started')) console.log('read file started') require('fs').readFile(__filename, () => console.log('file processing started')) setTimeout(() => console.log('timer fires'), 500) async function longLoop () { console.log('loop started') let res = 0 for (let i = 0; i < 1e7; i++) { res += Math.sin(i) // arbitrary computation heavy operation if (i % 1e5 === 0) await null /* solution: await new Promise(resolve => setImmediate(resolve)) */ } console.log('loop finished') return res } 

What if run ( node loop.js ) produces:

 loop started read file started loop finished loop result processing started timer fires file processing started 

How can this code be rewritten to read and process a file while the loop is running in the background?

My decision

I came up with the following:

 longLoop().then(res => console.log('loop result processing started')) console.log('read file started') require('fs').readFile(__filename, () => console.log('file processing started')) setTimeout(() => console.log('timer fires'), 500) async function longLoop () { let res = 0 let from = 0 let step = 1e5 let numIterations = 1e7 function doIterations() { //console.log(from) return new Promise(resolve => { setImmediate(() => { // or setTimeout for (let i = from; (i < from + step) && (i < numIterations); i++) { res += Math.sin(i) } resolve() }) }) } console.log('loop started') while (from < numIterations) { await doIterations() from += step } console.log('loop finished') return res } 

What is really logged:

 loop started read file started file processing started timer fires loop finished loop result processing started 

Is there an easier and more concise way to do this? What are the disadvantages of my solution?

+5
source share
1 answer

The reason the first version of your code blocks further processing is because await receives an immediately resolving promise (a null value ends with a promise, as if you were doing await Promise.resolve(null) ). This means that the code after await resume during the current "task": it simply pushes the microtask into the task queue, which will be consumed as part of the same task. All other asynchronous things that you expect are waiting in the task queue, not in the microtask queue.

This applies to setTimeout as well as to readFile . Their callbacks are expected in the task queue and, as a result, will not receive priority over the mircrotasks generated by await s.

So, you need a way to make await put something in the task queue, not the microtask queue. This can be done by giving him a promise that will not be immediately resolved, but will be resolved only after the current task.

You can enter this delay using .... setTimeout :

 const slowResolve = val => new Promise(resolve => setTimeout(resolve.bind(null, val), 0)); 

You would call this function with await . Here is a snippet that uses image loading instead of file loading, but the principle is the same:

 const slowResolve = val => new Promise(resolve => setTimeout(resolve.bind(null, val), 0)); longLoop().then(res => console.log('loop result processing started')) console.log('read file started') fs.onload = () => console.log('file processing started'); fs.src = "https://images.pexels.com/photos/34950/pexels-photo.jpg?h=350&auto=compress&cs=tinysrgb"; setTimeout(() => console.log('timer fires'), 500) async function longLoop () { console.log('loop started') let res = 0 for (let i = 0; i < 1e7; i++) { res += Math.sin(i) // arbitrary computation heavy operation if (i % 1e5 === 0) await slowResolve(i); } console.log('loop finished') return res } 
 <img id="fs" src=""> 
+3
source

Source: https://habr.com/ru/post/1270620/


All Articles