1

I want to let Javascript run two operations at the same time.

I want to let javascript process a large loop while running interval to do a timer.

I searched online and found that we only have two ways either using a event loop or using a web worker.

I have been struggled with it for a long time, but it is pretty for me to understand how to really use web worker or event loop.

For the event loop, I can't really understand the code the documentation it gives:

while (queue.waitForMessage()) {
  queue.processNextMessage()
}

What does the queue stand for? If I directly use queue in my code, it will display queue is not defined

Also, how could i use it actually it my code?

For the web worker, here is my code:

    let result = document.getElementById('result')
    let time = document.getElementById('time')
    let worker;
    let script = document.getElementsByTagName('script')
      let interval = setInterval(function(){
        time.textContent = parseFloat(time.textContent)+1
      },1000)
    function display(){
    for(let i =0;i<10000;i++){
      result.innerHTML +=i
    }
    }
let startworker = function(){
   if(typeof(worker)=='undefined'){
      worker = new Worker(script);
      //I think this will produce error, but I don't know how to deal with the onmessage to let it run the function
      worker.onmessage = function(){
        display();
      }
   }
}
 

<div id='result'></div>
<button onclick='startworker()'>Click</button>
  <div id='time'>0</div>
If my code, if I click the button, it will produce failed to construct 'Worker'. Also, for the onmessage part, I don't really have idea of how could I make it run the loop and I think my code is incorrect.

I have read a lot of articles and documentations, but I still don't have idea of how to do that.

Could anyone helps me to understand those two ways and give me a better way of solving this situation?

Thanks for any responds and forgive my ignorance!!!

The example:

let result = document.getElementById('result')
let time = document.getElementById('time')
let interval;
function start(){
   interval = setInterval(function(){
    time.textContent = parseFloat(time.textContent)+1
  },1000)
  setTimeout(reallystart(),0)
}
function reallystart(){
for(let i =0;i<10000;i++){
  if(i == 9999){
   window.clearInterval(interval)
  }
  result.innerHTML +=i
}
}
 

<button onclick='start()'>Click</button>
  <div id='result'></div>
  <div id='time'>0</div>
  • 1
    That event-loop code snippet you've given is part of the browser's code - not yours. The purpose is to illustrate how event processing works beneath the covers. Having a bunch of interval times running at the same time would see the events added to and removed from the queue. It also illustrates why some things dont happen when you want - there's still a backlog of unprocessed messages.. – enhzflep Nov 28 '21 at 22:25
  • I don't know what are you trying to do . running stuff in parallel means you run them at the same time and then wait for all of them to finish then run again . anyhow I think what you need is an async parallel function . it would be better to understand if you provide more info about your code with better example . – pfndesign Nov 28 '21 at 22:33
  • @pfndesign, what I expect to do is to run a timer while a function is processing –  Nov 28 '21 at 22:34
  • @jackssmith is it just a one-timer of a timer for each of your processes? you can run an interval then check if the process is running with a variable if is not running then remove the timer – pfndesign Nov 28 '21 at 22:36
  • @enhzflep, I know that the example it gives will absolutely not work in my own code. , but I still kind confused with how to really write the event loop in my own code even though I do a lot of researches –  Nov 28 '21 at 22:43
  • @pfndesign, I just updated my code so that you could know what I expect to get. –  Nov 28 '21 at 22:45
  • @jackssmith - The thing is, you don't write the event loop - the browser is the one that has it. You just need to generate and handle events. Th browser takes care of the details. – enhzflep Nov 28 '21 at 23:03
  • @enhzflep, but how could I really write it in my code? I think simply defined two events will not working. Sorry, I am suck. –  Nov 28 '21 at 23:06
  • 1
    @jackssmith - are you in a position to detail _what_ the two things are, that you'd like to do at the same time? If you really do need to do things concurrently, using a WebWorker seems like the way to go, since each worker gets it's own thread. Whereas using a message queue uses a single thread to wait for and process the messages - two timeouts that end at the same time will be handled one after the other. – enhzflep Nov 29 '21 at 01:00
  • @enhzflep, yes you are right. I want to run a `setInterval` (timer) while it is processing a `loop`. For my above code, it will only run the interval after the loop finish, but I want to let them run at a same time. I know web worker is also anothr way to do that, but it is pretty hard for me to understand how to use `web worker` as well even though I read a lot of documentations. Could you explain me a little bit about it and how do I really let my above code works (maybe in the answer section : D) Thanks so much! –  Nov 29 '21 at 01:26

1 Answers1

2

You don't have to write yourself an event-loop, you will make use of the browser's one.

Still, for your case of running a (synchronous) loop and a scheduled task concurrently, there are indeed two ways of doing it:

  • On a single thread, by breaking your loop in several tasks.
  • On multiple threads, by using Web Workers.

The event loop in a browser can ultimately be very roughly schematized as being a while loop indeed. At each iteration it will pick a new task and run it until completion (in facts it will pick the tasks from multiple task sources, so that a priority system can be set up).
Your JavaScript job is itself generally executed as part of such a task*, and the task won't end before this JavaScript job is itself ran to completion. This means that if your JavaScript job never ends, the event-loop won't be able to process the next task, because it will be stuck in the one that spawned your code.
setInterval (and setTimeout) do schedule a new task to execute the passed JavaScript at some time in the future. But once again, while your for loop is blocking the event-loop, this newly scheduled task won't ever be able to do its job.

To solve this issue, we thus have to not block the event loop in order to let it pick the newly scheduled tasks too.

To do this in a single event-loop, you will have to break your loop into small chunks and separate the execution of each chunks in their own tasks. This way, the event-loop will be able to determine if it has to execute other tasks than these ones:

let i = 0;
// for loop only updates i
const executeForLoop = async () => {
  for(; i < Infinity; i++) {
    // a chunk of 1000 loops
    if(i % 1000 === 0) {
      await waitNextTask();
    }
  }
};
executeForLoop();
// every 1s we update the displayed value
setInterval(() => {
  document.getElementById("log").textContent = i;
}, 1000);

// currently the fastest way to post a new task
// see https://stackoverflow.com/questions/61338780/
function waitNextTask() {
  return new Promise( (res) => {
    const channel = waitNextTask.channel ??= new MessageChannel();
    channel.port1.addEventListener("message", () => res(), { once: true }); 
    channel.port1.start();
    channel.port2.postMessage("");
  });
}
<pre id="log"></pre>

However doing this will still take a lot of the computation power that is normally affected to rendering the page correctly and handling all the user interactions, so use this only when you have no choice, e.g because the calculation requires access to data that is only accessible in the main thread like the DOM.

The better way to handle long running scripts is to use a so-called "dedicated worker". This will generally run on an other thread but will anyway have its own event-loop.
So you can block this Worker's event-loop all you like, and the main thread will keep working as if nothing was happening.

However, you need to execute the blocking script in the Worker, the worker.onmessage handler will still be executed in the main thread and it will still block the main event-loop.

So the previous example rewritten to use a Worker:

let i;
// every 1s we update the displayed value
setInterval(() => {
  document.getElementById("log").textContent = i;
}, 1000);
const worker = new Worker(getWorkerURL());
worker.onmessage = (evt) => {
  i = evt.data;
};

// because we can't link to external files in StackSnippets
// we use a dirty blob: URL hack
function getWorkerURL() {
  const elem = document.querySelector("[type='worker-script']");
  const content = elem.textContent;
  const blob = new Blob([content], { type: "text/javascript" });
  return URL.createObjectURL(blob);
}
<script type="worker-script">
// the hard computation is made in the script
// that will be used to initiate the Worker
let i = 0;
const executeForLoop = () => {
  for(; i < Infinity; i++) {
    // we keep a chunking logic
    // to avoid spamming the main thread with too many messages
    if(i % 1000 === 0) {
      postMessage(i);
    }
  }
};
executeForLoop();
</script>
<pre id="log"></pre>

*JS jobs may also be called as a callback in the update-the-rendering steps where it's technically not linked to a task, but there is no sensible difference for us web-authors.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thanks for the answer. I am not sure if it is my problem ,but in the `web worker` example, The `script type="worker-script"` won't be parsed by js engine so the `Cannot read properties of null (reading 'textContent') at getWorkerURL` will produced `null`. I am not sure why this work in `stackoverflow`. I use sublime text and the code won't work even though I created a new html file and directly copy your code –  Nov 29 '21 at 22:52
  • The html part of the snippet needs to be set before the javascript in a standalone html file. StackOverflow's StackSnippets do the swapping. But in your case you don't even need this part of the script, use an external file directly, call it e.g `worker.js`, paste the content of the ` – Kaiido Nov 30 '21 at 00:50