0

I have a JavaScript function which does fetch a couple of hundred(!) URLs.

The function must fetch these URLs in sequence, so it has to wait after one fetch before it attempts the next fetch.

How can I design this function or how can I call it, so that I do not get the browser warning "Unresponsive Script"?


Note: Because the page with the script gets used by many users I cannot change browser-specific settings (like dom.max_script_run_time)

halloleo
  • 9,216
  • 13
  • 64
  • 122
  • 1
    Use the microtask queue. FYI https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context#:~:text=task%20in%20microtask%20queue%20will,task(a%20macrotask)%20too. – marekful Mar 17 '21 at 07:36
  • 1
    In practice this may be achieved by a couple of different approaches, a good idea could be sequentially resolved promises. Check this: https://stackoverflow.com/a/41115086/1207049 – marekful Mar 17 '21 at 07:45

1 Answers1

3

Running long async scripts prevents paint of the page. So you need to convert your tasks into "batches" and let the browser process other tasks after running each batch.

Look at this example from https://javascript.info/event-loop :

let i = 0;

function count() {
    // do a piece of the heavy job (*)
    do {
      i++;
      progress.innerHTML = i;
    } while (i % 1e3 != 0);

    if (i < 1e7) {
      setTimeout(count);
    }
}

count();

Now the heavy task is done in batches of 1000 iterations instead of one batch of 1e7 iterations.

In the code we are saying that:

  • Increase i if it's not a multiplication of 1000. But the code progress.innerHTML = i doesn't update the interface before the loop ends.
  • As soon as i is a multiplication of 1000, the loop breaks, another batch is "postponed" using setTimeout and the UI is updated.

You can also move your code into a web worker which runs on a background thread and doesn't interfere with the user interface.

UPDATE: The setTimeout method shown above works but it's not a perfect solution because:

The main script of JS is run on a single thread and async actions aren't run on another thread, they're just postponed to be run "later". So they "always" block the main script when they're running, even if we do the mentioned hack (the paint is blocked when we're in the do-while loop).

If we want to "really" separate that heavy task from our main script, we should use Web Workers which are really run on another OS-level thread.

So your best choice is to use a Web Worker and communicate the results using postMessage system.

Note: Web Workers do not have access to the DOM

pouria
  • 949
  • 8
  • 21
  • Thanks. Will try it out. What are the advantages of the “suspend” design you show in your code and what the advantages of a web worker? – halloleo Mar 17 '21 at 12:55
  • @halloleo I updated the answer. I hope it answers your question – pouria Mar 17 '21 at 21:51
  • Cool. Thank for the update as well. - In my case I used the event-loop model. One thing to be aware of is that all changing variables (in the case of the sample just the varaible `i`) have to be globals, so that they survive the various `count` executions. – halloleo Mar 19 '21 at 05:19