Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are javascript promises asynchronous when calling only synchronous functions?

In testing I've found that JavaScript Promises are always asynchronous regardless of whether or not they contain any asynchronous functions in their chain.

Here is some code that shows the order of operations in console. If you run it you will see that even though every function is synchronous the output shows both of the aPromise() calls being run in parallel, and "surprisingly this happens after run 2 finishes" not happening before run 2 finishes.

function aPromise() {
  return new Promise(function(resolve, reject) {
    console.log("making promise A")
    resolve(bPromise());
    console.log("promise A resolved")
  });
}


function bPromise() {
  return new Promise(function(resolve, reject) {
    console.log("making and resolving promise B")
    resolve();
  });
}

aPromise().then(function() {
  console.log("finish run 1");
}).then(function() {
  console.log("surprisingly this happens after run 2 finishes");
});
aPromise().then(function() {
  console.log("finish run 2");
})

Output to console:

making promise A
making and resolving promise B
promise A resolved
making promise A
making and resolving promise B
promise A resolved
finish run 1
finish run 2
surprisingly this happens after run 2 finishes

So, Why are JavaScript promises asynchronous when calling only synchronous functions? What is happening behind the scenes that leads to this behavior?


P.S. In order to better understand this I implemented my own Promise system and I found that making synchronous functions happen in the expected order was easy but making them happen in parallel was something I could only accomplish by putting a setTimeout() of a few milliseconds at every resolve (My guess is that this is not what's happening with vanilla promises and that they are actually being multi threaded).

This has been a small problem for one of my programs where I'm traversing a tree applying an array of functions to each node and putting the functions in queue if that node has an asynchronous function already running. Most of the functions are synchronous so the queue is rarely used but upon switching over from callbacks (hell) to Promises I've been having an issue where the queues get used almost always as a result of Promises never running synchronously. It's not a huge problem but it is a bit of a debugging nightmare.

1 Year Later EDIT

I ended up writing some code to deal with this. It's not amazingly thorough, but I've used it with success to solve the issue I was having.

var SyncPromise = function(fn) {
    var syncable = this;
    syncable.state = "pending";
    syncable.value;

    var wrappedFn = function(resolve, reject) {
        var fakeResolve = function(val) {
            syncable.value = val;
            syncable.state = "fulfilled";
            resolve(val);
        }

        fn(fakeResolve, reject);
    }

    var out = new Promise(wrappedFn);
    out.syncable = syncable;
    return out;
}

SyncPromise.resolved = function(result) {
    return new SyncPromise(function(resolve) { resolve(result); });
}

SyncPromise.all = function(promises) {
    for(var i = 0; i < promises.length; i++) {
        if(promises[i].syncable && promises[i].syncable.state == "fulfilled") {
            promises.splice(i, 1);
            i--;
        }
        // else console.log("syncable not fulfilled" + promises[i].syncable.state)
    }

    if(promises.length == 0)
        return SyncPromise.resolved();

    else
        return new SyncPromise(function(resolve) { Promise.all(promises).then(resolve); });
}

Promise.prototype.syncThen = function (nextFn) {
    if(this.syncable && this.syncable.state == "fulfilled") {
            //
        if(nextFn instanceof Promise) {
            return nextFn;
        }
        else if(typeof nextFn == "function") {
            var val = this.syncable.value;
            var out = nextFn(val);
            return new SyncPromise(function(resolve) { resolve(out); });
        }
        else {
            PINE.err("nextFn is not a function or promise", nextFn);
        }
    }

    else {
        // console.log("default promise");
        return this.then(nextFn);
    }
}
like image 712
Seph Reed Avatar asked Apr 19 '16 18:04

Seph Reed


People also ask

How does Asynchronous JavaScript run?

Let us see the example how Asynchronous JavaScript runs. So, what the code does is first it logs in Hi then rather than executing the setTimeout function it logs in End and then it runs the setTimeout function. At first, as usual, the Hi statement got logged in.

What are promises in JavaScript async?

All asynchronous operations in JavaScript are based on Promises and their chained callback operations. According to MDN Documentation, “A Promise is a proxy for a value not necessarily known when the promise is created.

What is the difference between synchronous and asynchronous code?

This means the synchronous code runs line by line giving no room for asynchronous code to wait and give a response (the promise). You have to wrap an asynchronous function around the asynchronous function you are calling to get the desired result.

Is asynchronous JavaScript delaying your user interface?

This may not look like a big problem but when you see it in a bigger picture you realize that it may lead to delaying the User Interface. Let us see the example how Asynchronous JavaScript runs.


Video Answer


1 Answers

The callback passed to a Promise constructor is always called synchronously, but the callbacks passed into then are always called asynchronously (you could use setTimeout with a delay of 0 in a userland implementation to achieve that).

Simplifying your example (and giving the anonymous function's names so I can refer to them) to:

Promise.resolve().then(function callbackA () {
  console.log("finish run 1");
}).then(function callbackB () {
  console.log("surprisingly this happens after run 2 finishes");
});

Promise.resolve().then(function callbackC () {
  console.log("finish run 2");
})

Still gives the output in the same order:

finish run 1
finish run 2
surprisingly this happens after run 2 finishes

Events happen in this order:

  1. The first promise is resolved (synchronously)
  2. callbackA is added to the event loop's queue
  3. The second promise is resolved
  4. callbackC is added to the event loop's queue
  5. There is nothing left to do so the event loop is accessed, callbackA is first in the queue so it is executed, it doesn't return a promise so the intermediate promise for callbackB is immediately resolved synchronously, which appends callbackB to the event loop's queue.
  6. There is nothing left to do so the event loop is accessed, callbackC is first in the queue so it is executed.
  7. There is nothing left to do so the event loop is accessed, callbackB is first in the queue so it is executed.

The easiest way I can think of to work around your problem is to use a library that has an Promise.prototype.isFulfilled function you can use to decide whether to call your second callback synchronously or not. For example:

var Promise = require( 'bluebird' );                                                                                                                          

Promise.prototype._SEPH_syncThen = function ( callback ) { 
    return (
      this.isPending()
        ? this.then( callback )
        : Promise.resolve( callback( this.value() ) ) 
    );  
}

Promise.resolve()._SEPH_syncThen(function callbackA () {
  console.log("finish run 1");
})._SEPH_syncThen(function callbackB () {
  console.log("surprisingly this happens after run 2 finishes");
});

Promise.resolve()._SEPH_syncThen(function callbackC () {
  console.log("finish run 2");
})

This outputs:

finish run 1
surprisingly this happens after run 2 finishes
finish run 2
like image 55
Paul Avatar answered Nov 12 '22 02:11

Paul