Search code examples
typescriptasync-awaitpromise

javascript promises: deferring a syncronous, resource-intensive computation


I'd like to wrap some long-running, syncronous computation, such that a caller can be free to perform other calls while that computation is executing.

Here is some sample code:

const longRunning = ms => {
    const end = Date.now() + ms
    while (Date.now() < end) continue
}

const asyncWrappedA = async (ms) =>
    new Promise<void>(resolve => {
        longRunning(ms);
        resolve();
    });

const asyncWrappedB = async (ms) =>

    new Promise<void>(resolve => {
        setTimeout(() => {
            longRunning(ms);
            resolve()
        }, 0);
    });

(async () => {
    const funcs = [asyncWrappedA, asyncWrappedB];

    for (const i in funcs) {
        const name = funcs[i].name;
        const start = Date.now();
        const promise = funcs[i](1000);
        console.log(name + ": promise returned: +" + (Date.now() - start));
        promise.then(
            () => {console.log(name + " then: +" + (Date.now() - start));}
        );
    }
}
)()

Output:

asyncWrappedA: promise returned: +1000
asyncWrappedB: promise returned: +0
asyncWrappedA then: +1002
asyncWrappedB then: +1002

As you can see, using setTimeout (asyncWrappedB) has the desired effect. I expected that merely wrapping the call with new Promise will do the trick, but that doesn't seem to cut it.

Can someone elucidate:

  1. why is it that asyncWrappedA doesn't return immediately?
  2. What would be a better way to achieve the goal, without using setTimeout (which seems like a hack imo)?

Many thanks


Solution

  • why is it that asyncWrappedA doesn't return immediately?

    The function that you pass to new Promise is always executed synchronously. In almost all cases, what you want to do in a promise constructor is set up some callback to happen at a later time, and then you immediately return. This is what you did in the code that works:

    resolve => {
      setTimeout(() => {
        longRunning(ms);
        resolve()
      }, 0);
    }
    

    But in your other code you're doing a busy loop, so the promise constructor cannot finish until it's complete:

    resolve => {
      longRunning(ms);
      resolve();
    }
    

    What would be a better way to achieve the goal, without using setTimeout (which seems like a hack imo)?

    For the most part, javascript is single threaded, so if you're going to be running in the main thread while still wanting other code to be able to execute, the best you can do is delay or spread out when you're doing your work so that other code can be run in between. This is done with setTimeout, or for some specialized cases requestAnimationFrame.

    Other than that, another option to consider is moving the code to a Web Worker thread. But that can be combersome, so i would start with trying to stagger the work using setTimeout first.