94

I've observed that in the following code:

setTimeout(function(){console.log('setTimeout')});
Promise.resolve(1).then(function(){console.log('promise resolve')})

No matter how many times I execute this, the promise callback always logs before the setTimeout.

My understanding is that both callbacks are scheduled to be executed to the next tick, and I don't really understand what is going on that makes the promise always take precendence over the timeout.

weisk
  • 2,468
  • 1
  • 18
  • 18
  • 9
    `Promise` is getting `resolved` as soon as it is created whereas `setTimeout` comes later in the queue.. – Rayon Aug 03 '16 at 20:03
  • 1
    Read up on the internal queue/event loop: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop, http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/ or watch this: https://www.youtube.com/watch?v=8aGhZQkoFbQ – Rob M. Aug 03 '16 at 20:06
  • Does this answer your question? [Difference between microtask and macrotask within an event loop context](https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context) – Liam Jun 04 '20 at 08:10
  • 1
    @Rayon "Promise is getting resolved as soon as it is created" this is not true, the callback inside `then()` will be put at the end of the current microstask queue. The rest of the current callstack and the tasks currently present in the microtask queue will be executed before. – Ricola Mar 25 '23 at 16:41
  • @Ricola - I agree. I meant it by "as soon as it is created". – Rayon Mar 25 '23 at 16:44

6 Answers6

111

Promise.resolve schedules a microtask and the setTimeout schedules a macrotask. And the microtasks are executed before running the next macrotask.

JamesQMurphy
  • 4,214
  • 1
  • 36
  • 41
Mohamed Gara
  • 2,665
  • 3
  • 13
  • 19
  • 17
    That's closest answer. I would add link to deep explanation of tasks/microtasks. https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ – artin Oct 10 '17 at 17:14
  • 3
    Also worth mentioning right away that source of microtasks can be only 2 things: MutationObserver and Promises. – artin Oct 10 '17 at 17:23
  • Read this https://javascript.info/event-loop for full understanding. – NiRUS Jul 24 '22 at 00:30
53

Short answer Promises have better priority than setTimeout callback function in event loop stack(or how i understand it).

Long answer watch this video. Very helpful. Hope this helps.

https://www.youtube.com/watch?v=8aGhZQkoFbQ

Thanks @MickJuice for new and updated video for event loop.

https://www.youtube.com/watch?v=cCOL7MC4Pl0

Mykola Borysyuk
  • 3,373
  • 1
  • 18
  • 24
  • 7
    That is a great video. So is this one to help you understand priorities with tasks and events: https://www.youtube.com/watch?v=cCOL7MC4Pl0 – MickJuice Apr 25 '18 at 21:42
  • 2
    A good article mentioned in a comment on another answer: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ – Chris May 09 '18 at 03:59
24

setTimeout() has a minimum delay of 4ms, so even though you didn't specify a delay in your code the timeout will still be delayed at least 4ms. Your promise's .then() is called in the meantime.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • 8
    Even if there was no 4ms throttling optimization, promises will be faster anyway. Because `setTimeout` creates task and `Promise` creates microtask (job). That's real reason. – artin Oct 10 '17 at 17:11
  • 1
    Well... OK, but in practice the 4ms minimum *does* apply so for examples like in the question the macro versus micro task priory doesn't enter into it. If we're going to imagine different behaviour than what is actually implemented then even if promises had a lower priority than timeouts the promise in the example would still log first because of the 4ms minimum timeout. Still, back in the real world it is useful to know that the micro task concept exists in JS so thanks for mentioning it. – nnnnnn Oct 10 '17 at 23:36
  • 1
    `meantime` ... to be more precise: at the next tick – Jonas Wilms Mar 26 '18 at 15:51
8

Timeouts and Promises both serve to execute code in an asynchronous way but with differences characteristics and purposes:

setTimeout - Delays the execution of a function by specific time duration. - Does not block the rest of the code execution (asynchronous behavior) - They create Macrotask (browser internal operation)

Promises - They are a wrapper to allow asynchronous execution of code(Eg: an ajax call). (Does not depend on specific time duration) - They are especially useful to chain different async calls. - Does not block the rest of the code execution (asynchronous behavior) at less you are using the await operator. - They create Microtask (browser internal operation), which have priority over the Macrotask.

Recommendation

  • Use setTimeout when you want to delay a function execution some specific time duration and not block the rest of the code execution in the process

  • Use Promises: When you want to execute some async code and to avoid the “callback hell” (yes because you can make asynchronous ajax calls without Promises but the syntax is less clear and more prone to errors)

Juanma Menendez
  • 17,253
  • 7
  • 59
  • 56
  • 1
    It's worth mentioning that Promise executor-the function passed to `new Promise(...)`- runs synchronously. – Alireza Jun 22 '21 at 01:20
5

This has to do with the event loop as defined in the Web Spec. The browser has multiple task queues for multiple types of tasks (e.g. timer tasks created through setTimeout), as well as a microtask queue (where Promise settlement gets pushed to). Whenever the browser finishes executing a task, it empties the microtask queue and executes all tasks in it, before continuing with a task from another task queue.

Therefore after the code executes (which is a task), the Promise settlement is inside of the microtask queue, and the timer task might already be inside a task queue¹. The microtask queue gets emptied and the Promise resolves. Then somewhen the timer task runs.

¹ Browsers might choose to increase timeouts a bit, and they do. A timeout will never run after 0ms in most browsers.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
3

Timeouts and Promises serve different purposes.

setTimeout delays the execution of the code block by a specific time duration. Promises are an interface to allow async execution of code.

A promise allows code to continue executing while you wait for another action to complete. Usually this is a network call. So anything in your then() call will be executed once the network call (or whatever the promise is waiting for) is completed. The time difference between the start of the promise and the resolution of the promise entirely depends on what the promise is executing, and can change with every execution.

The reason the promise is executing before your timeout is that the promise isn't actually waiting for anything so it resolved right away.

jfadich
  • 6,110
  • 2
  • 20
  • 31
  • 3
    By that logic, in this code: `Promise.resolve(2).then(function(){console.log('promise resolve 2')}); console.log('Immediate')` "promise resolve 2" would be logged before "immediate" right? but it doesnt – weisk Aug 03 '16 at 20:07
  • 5
    @frankies That has more to do with the way Promises are queued and resolved. The focus of my answer is the difference between `setTimeout` and `Promise`. – jfadich Aug 03 '16 at 20:11