shau-kote
shau-kote

Reputation: 1150

Can I rely on non-chained subsequent promises order?

It is known one can combine multiple Promises into a chain and thereby call onFulfilled callbacks exactly one by one (despite they will be asynchronous from each other):

Promise.resolve()
    .then(() => {
       console.log("i will be the first one");     
    })
    .then(() => {
       console.log("i will be the second one");  // definetely second
    })
;

But what about non-chained subsequent Promises? The simplest example:

Promise.resolve()
   .then(() => console.log("i will be the first one"))
;
Promise.resolve()
   .then(() => console.log("i will be the second one"))  // sure?
;

In my naive opinion, Promises callbacks works via event queue (about like timer event inside setTimeout) and in that way first Promise.resolve() push its event in queue before second one do this, therefore first callback will be called before second one.

But I am not sure there are any guarantees about it. Can I rely on it or it is an async lotto? Does someone know what specs tell about it?

UPDATE

Some of you noticed is the simplest example is useless so I want to explain my original problem.

I have a class which lazy initializes instance of another class and provides get method for the hosted instance:

class Lazy {

    /** @param {Class} T */
    constructor(T) { }

    /** @returns {Promise.<T>} */
    instance() { 
       // there will be complex async initialization at first call
       // and Promise.resolve() at following calls
    }
}

class Foo {
   on() { }
   off() { }
}

/** @type {Lazy.<Foo>} */
let foo = new Lazy(Foo);

foo.instance().then((i) => i.on());
foo.instance().then((i) => i.off());

Last two lines reveal my problem - it is difficult to work withFoo instance in that way when I am not sure that on() will be called before off().

Upvotes: 1

Views: 58

Answers (2)

Liam
Liam

Reputation: 29750

Promises callbacks works via event queue

This isn't true. Callbacks work on a first returned first served basis. The ordering of when you initiate the operation is unimportant, it's the order that they return that decides which operation will be processed first.

In your example if your "first" async operation takes longer to return than your "second" async operation then the "second" then will be processed first.

Obviously there are lots of variables on how long these operation will take to return. Network speed, load on the server (or whatever async service your using), the browsers implementation of the promise engine, etc. etc. So you have to assume that you have no idea when these will be processed.

To avoid race conditions if you need something to be in a specific order then use callbacks/.then/await/etc to ensure that they run in that order. You cannot rely on the order that you call the operation.

Upvotes: 1

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276506

Your actual issue:

Last two lines reveal my problem - it is difficult to work withFoo instance in that way when I am not sure that on() will be called before off().

You should not rely on this behaviour, instead you should await for obtaining the instance and then cache it:

async function withInstance() {
  let instance = await foo.instance();    
  await instance.on();
  await instance.off(); // guaranteed to be called after `.on`.
}

What you asked

You can rely on execution order of Jobs.

When a promise then is added it's an EnqueueJob in the spec. Quoting Jobs and Job Queues:

The PendingJob records from a single Job Queue are always initiated in FIFO order.

Note this guarantee does not hold if you have multiple contexts (for example - different promises between iframes or workers).

That said - it is highly not recommended to rely on the execution order like that.

Upvotes: 1

Related Questions