7

I have a function, that looks like this.

 function () {
      longArray.forEach( element => doSomethingResourceIntensive(element))
 }

Because the array is long and the function is a little resource intensive, it freezes the browser.

Now I want to rewrite it using Promises, so it does the same thing, just not freezing the browser, and I want the solution to be elegant and "ES6-y"; ideally, the function would return Promise when all the iterations finished.

I found this question, where it's dealt with using setTimeout, but it seems a little "un-ES6-y", and it doesn't return a Promise.

I cannot do

 function () {
      return Promise.all(longArray.map( element => 
          Promise.resolve().then(() => doSomethingResourceIntensive(element))
      )
 }

because I need to run the promises in succession and I am not sure if it would happen there.

Karel Bílek
  • 36,467
  • 31
  • 94
  • 149
  • Did you try using `setTimeout` and executing the loop inside? – jeerbl Nov 12 '15 at 11:50
  • 4
    What about [Web workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker)? – Andreas Nov 12 '15 at 11:52
  • 5
    Promises are a way to deal with asynchronous operations, they won't magically prevent long running JavaScript from freezing the browser. You'll have to rewrite your code in a non-blocking way, for example with web workers or with `setTimeout` / `setImmediate`. – Paolo Moretti Nov 12 '15 at 12:06
  • @PaoloMoretti Hm, yeah, but at least when I added my own function `timeoutPromise` (which does what you would expect) it starts actually doing timeouts and works reasonably well, while still looking elegant – Karel Bílek Nov 12 '15 at 12:37
  • 2
    Neither promises nor timeouts can prevent the browser from freezing. The code still has to run. The only thing you can achieve with timeouts is partitioning of the workload, which can be an improvement if the units of work are small enough. Web workers are the only solution for large workloads, because they offload the work to another thread. – a better oliver Nov 12 '15 at 13:29

2 Answers2

4

If you need to run promises in succession, you want to chain the .then calls. You normally do that with .reduce():

function () {
    return longArray.reduce((promise, el) => 
        promise.then(() => doSomethingResourceIntensive(el)),
      Promise.resolve()); // Start with a clean promise!
}

Also, depending on the type of job you do, you may want to have a look into Web Workers, which are executed in another thread and thus don't block the page.

Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
  • 1
    This seems very promising (no pun intended), I will probably use this. Will accept after I try it – Karel Bílek Nov 12 '15 at 11:51
  • I think you missed `return` before `promise.then` – Sean Nov 12 '15 at 11:53
  • @Sean No, I just had redundant curly brackets and semicolons. Arrow functions handle returns if there's only one statement. – Madara's Ghost Nov 12 '15 at 11:53
  • do you think it will be better to partition the longArray in small chunk, and process the chunk with promise? – Sean Nov 12 '15 at 11:58
  • @Sean Not really, unless you can actually physically cut the work down into smaller chunks, it doesn't matter. The memory footprint will remain the same if you have the same number of tasks. In this scenario, each task will run only when the previous one was finished anyway. – Madara's Ghost Nov 12 '15 at 12:01
  • @MadaraUchiha I ended up making short helper function timeoutPromise(i) that resolves after i milliseconds, and I added it to your code, and it actually works great – Karel Bílek Nov 12 '15 at 12:36
  • 1
    @KarelBílek You should probably consider using bluebird. It already has that helper (named `Promise.delay()`) and a lot more very useful features. – Madara's Ghost Nov 12 '15 at 12:39
  • `.then` chains alone wont fix it. They execute on a microtask queue which in most browsers are emptied completely at the tail of the current run-to-completion. In other words, they'll still freeze things. You need `setTimeout`. – jib Nov 13 '15 at 03:27
2

The answer you reference is right, you need setTimeout. Using a promise-chain alone wont help because .then chains execute on a microtask queue, which in most browsers are emptied completely at the tail of the current run-to-completion. In other words, they'll still freeze things.

If you want something ES6-y, I rely on this trusty helper:

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));

Then I can do this:

longArray.reduce((p, i) => p.then(() => doIntensiveStuff(i)).then(() => wait(5)),
                 Promise.resolve());

Unless you can use workers of course.

jib
  • 40,579
  • 17
  • 100
  • 158