2

The spec says (para 5):

The PendingJob records from a single Job Queue are always initiated in FIFO order. This specification does not define the order in which multiple Job Queues are serviced. An ECMAScript implementation may interweave the FIFO evaluation of the PendingJob records of a Job Queue with the evaluation of the PendingJob records of one or more other Job Queues.

Does this mean I can't count on the callback supplied to .then being evaluated before a callback supplied to setTimeout in an otherwise synchronous control flow?

In other words, can I depend on the following printing one two.

setTimeout(() => console.log('two'));
Promise.resolve().then(() => console.log('one'));
Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • related: [Difference between microtask and macrotask within an event loop context](https://stackoverflow.com/q/25915634/1048572) – Bergi May 08 '18 at 18:04

3 Answers3

6

Does this mean I can't count on the callback supplied to .then being evaluated before a callback supplied to setTimeout in an otherwise synchronous control flow?

Yes, that's what it means; the spec doesn't require that implementations work that way.

But in practice, the implementations with native Promise support I've tested it on have scheduled the then callback (a "microtask" from the PendingJobs queue) immediately after finishing the "macrotask" that scheduled it, before other pending macrotasks, even when the pending macrotask was scheduled before the microtask. (setTimeout and events are macrotasks.)

E.g., in the environments where I've tested it, this outputs A, C, B reliably:

console.log("A");
setTimeout(_ => console.log("B"), 0);
Promise.resolve().then(_ => console.log("C"));

But the JavaScript spec doesn't require it.

As Bergi points out, for user agent environments, the HTML5 spec covers this in its specification for microtasks and macrotasks. But that's only applicable to user agent environments (like browsers).

Node doesn't follow that spec's definition, for instance (not least because its timer functions return objects, not numbers), but Node also gives us A, C, B above, because (thanks Benjamin Gruenbaum!) it runs promise resolutions after the nextTick queue but before any timer or I/O callbacks. See his gist for details.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 2
    Note that in NodeJS promises are run _after_ the _nextTick_ queue but before any timers or IO. Note that the ES spec leaves it to the _platform_ to decide this - so NodeJS and Chrome work _slightly_ differently. – Benjamin Gruenbaum Apr 24 '17 at 16:21
  • @BenjaminGruenbaum: Thanks, very useful. Is the only difference the `nextTick` queue, which Chrome doesn't have? – T.J. Crowder Apr 24 '17 at 16:26
  • 1
    Close, but all event handlers for actions that happen the same time (timers, I/O) fire before any nextTick slot. I've made this gist for more info: https://gist.github.com/benjamingr/9afb9c52facd31484d5fc2e382de44c7 – Benjamin Gruenbaum Apr 24 '17 at 16:43
2

Yes, that's what it means - an other event might fire before the promise callback.

No, that won't happen - while ECMAScript allows it, the setTimeout spec does not.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I always forget `setTimeout` is not in the spec! – Ben Aston Apr 24 '17 at 15:48
  • 1
    How/where does the `setTimeout` spec say what happens when a *promise* resolution is queued? – T.J. Crowder Apr 24 '17 at 15:50
  • 1
    The following seems relevant: "As such, user agents must instead use the following definition in place of that in the JavaScript specification. These ensure that the promise jobs enqueued by the JavaScript specification are properly integrated into the user agent's event loops." https://html.spec.whatwg.org/multipage/webappapis.html#integration-with-the-javascript-job-queue – Ben Aston Apr 24 '17 at 15:55
  • @T.J.Crowder I meant that the whole of the HTML5 spec defines the relation between promise and timeout callbacks, using its concept of micro- and macrotask queues. – Bergi Apr 24 '17 at 16:07
  • @BenAston: Yes, that looks good -- for user agent environments. Node doesn't follow that spec for its timers (not least that their return values aren't numbers). But it does schedule promise resolution callbacks as microtasks. :-) – T.J. Crowder Apr 24 '17 at 16:07
  • Yeah. Is there an equivalent document for Node.js? – Ben Aston Apr 24 '17 at 16:16
  • 1
    @BenAston I don't think so, there is no "specification" for that particular implementation. The [documentation](https://nodejs.org/api/timers.html) says nothing about promises – Bergi Apr 24 '17 at 16:21
0

setTimeout does not mean that the supplied function will be executed after the provided time. It adds the function to the end of the queue once the delay has elapsed.

It really depends on when your promise resolves, as to the execution of the two statements. In your example, setTimeout will add it's callback to the queue ahead of the resolved promise, so you can expect one two.

Kyle Richardson
  • 5,567
  • 3
  • 17
  • 40