Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my chained promise blocking?

I have a class method that chains together other methods in the class, and also calls a method on another instance of the class:

class Thing {
    doSomething(nextThing) {
        return new Promise((resolve) =>
            this.initialize()
                .then(() => this.doA())
                .then(() => {
                    nextThing.initialize(); // call initialize() on another instance
                    return this.doB();
                })
                .then(() => this.doC())
                .then(resolve)
        );
    }

    initialize() {
        return new Promise((resolve) => {
            // take a long time to do something
            // ...
            // ...
            resolve();
        });
    }

    doA() { return new Promise((resolve) => resolve()); }

    doB() { return new Promise((resolve) => resolve()); }

    doC() { return new Promise((resolve) => resolve()); }
}

const thing1 = new Thing();
const thing2 = new Thing();

thing1.doSomething(thing2);

Calling the function on the other class instance locks up the flow of the chain, however; this.doB() and nextThing.initialize() will run simultaneously (as desired), but this.doC() won't run until nextThing.initialize() has resolved.

What's the right way to make sure this flows as expected, with nextThing.initialize() and this.doB() starting simultaneously, and starting this.doC() immediately after this.doB() resolves? It's okay if nextThing.initialize() resolves after this.doC().

like image 781
j0hnm4r5 Avatar asked Jan 03 '23 06:01

j0hnm4r5


1 Answers

When you do this structure:

return new Promise(resolve => {
    // run some long synchronous piece of code
    resolve(...);
});

Here's what happens.

  1. A new promise object is created
  2. The promise executor callback is called synchronously as part of the promise constructor execution
  3. Your long-running synchronous code is called om that executor callback
  4. You call resolve(...) to resolve the previously created promise
  5. You return from the promise executor
  6. The promise constructor returns
  7. You return from the host function and the line of code after this function call will get to run
  8. Sometime later (after the current piece of Javascript returns control back to the system), the .then() handlers are called on the previous promise.

So, a promise calls the executor callback synchronously. It doesn't allow you to "run anything in the background". Javascript is still single threaded.

You can't use a promise to make synchronous code into asynchronous code. You can use some promise techniques to change the scheduling of when code runs, but synchronous code in Javascript is still synchronous and blocking code in Javascript no matter when it runs.

Promises are purely a notification system for notifying you when some other operation has told a promise that it is now resolved or rejected. They don't magically convert synchronous code into asynchronous code.

So, bottom line, you can't use promises to take a synchronous, long-running initialize() function and somehow make it non-blocking or asynchronous.

What's the right way to make sure this flows as expected, with nextThing.initialize() and this.doB() starting simultaneously,

If nextThing.initialize() is synchronous and blocking, it can't run simultaneous with anything. node.js runs your Javascript single threaded. One piece of Javascript running at a time. Promises can't change that.

and starting this.doC() immediately after this.doB() resolves

Since this.doB() and this.doC() both return promises, then you chain the promises with chained .then() handlers to sequence those operations. Your code appears to already do that.

For info about options for off-loading long running synchronous code outside the current node.js single Javascript thread, see this other answer:

Make time intensive function asynchronous


FYI, perhaps this is just pseudo code, but there's never a reason to so this:

return new Promise((resolve) => resolve());

You can instead just do:

return Promise.resolve();.
like image 175
jfriend00 Avatar answered Jan 05 '23 18:01

jfriend00