1

I am confused with the behavior of Promise in JavaScript and have some questions, could someone help me to figure them out?

  1. These are two common ways to create Promise.

    function promise(){
        return new Promise((resolve, reject) => {
            resolve()});
    }
    
    let promise = new Promise(function(resolve, reject){
        resolve();
    });
    

    The first function creates a Promise object but doesn't call it until we call the function. In comparison, the second statement creates a Promise object and call it immediately. Am I right?

  2. For this function:

    function timeout(ms){
        return new Promise((resolve, reject) => {
            setTimeout(resolve, ms, 'done');
    });
    }
    
    timeout(100).then((value) => {
        console.log(value);
    });
    

    I think when we call the timeout function, it firstly creates an asynchronous function setTimeout and pushes it at the tail of the event queue. After all synchronous events finished, it will be called and creates a Promise object. The Promise object will also be pushed at the tail of the event queue, which is after other synchronous events. So there seems to be two event loops, am I right?

  3. Are these two statements equal?

    let promise = new Promise(function(resolve, reject){
        resolve();
    });
    
    let promise = Promise.resolve();
    
  4. Why is the output sequence like the following?

    setTimeout(function(){
        console.log('3');
    }, 0);
    
    Promise.resolve().then(function(){
        console.log('2');
    });
    
    console.log('1');
    
    // 1
    // 2
    // 3
    

    That is an example in a book, the author explains as following: the setTimeout(fn, 0) will be called at the beginning of the next event loop, the Promise.resolve() will be called at the end of current event loop and the console.log() will be called immediately.

    I am confused with why the setTimeout function will be called at the beginning of the next event loop. I think it is also an asynchronous function and will be pushed at the end of current event queue, so it should be called before the Promise object. Could someone tell me why I am wrong.

Aurora
  • 423
  • 2
  • 5
  • 12
  • 1
    Regarding 1), a Promise instance is created when `new Promise()` is called. The first way creates a function that will do this when called, the second way simply directly creates a Promise. This fact has nothing to do with how promises work, the same would be true for `new Date()` –  Dec 24 '18 at 23:02
  • 2
    yes, no, yes, there is more than one event queue (one for "tasks" and one for "mikrotasks" one nodejs there are even more) – Jonas Wilms Dec 24 '18 at 23:02
  • `setTimeout(fn, 0);` is actually a `setTimeout(fn, 10);` setTimeout doesn't support timeouts under 10 ms + it's not a *"call `fn` in 10ms"* but more a *"call `fn` as soon as you've got some time in no less than 10ms"* – Thomas Dec 24 '18 at 23:13
  • Possible duplicate of [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) – lleon Dec 24 '18 at 23:16

2 Answers2

2

The first function creates a Promise object but doesn't call it until we call the function. By comparison, the second statement creates a Promise object and call it immediately. Am I right?

The first function is just a function. As long as it is not called no promise is created. As soon as it is called, there is not much of a difference with the direct execution you put as alternative.

I think when we call the timeout function, it firstly creates an asynchronous function setTimeout

setTimeout is not created, the function exists as a built-in function. You can say it is called.

... and pushes it at the tail of the event queue.

It is not the function setTimeout itself that is pushed unto the queue. It is the callback (resolve in this case), including arguments, timeout period and the unique timer ID that is put in the list of active timers.

After all synchronous events finished, it will be called and creates a Promise object.

The Promise object is created during the call of the timeout function.

The callback that is passed to new Promise is usually called the promise constructor callback, it is at the moment that new Promise is executed that also that callback is executed (synchronously) and the promise is created.

When synchronous code has finished, i.e. when the call stack is empty, the microtask queue is consumed first. At this stage there is nothing there, so then the task queue is verified. If by that time an active timer has expired, there will be an entry for it in the task queue.

The Promise object will also be pushed at the tail of the event queue, which is after other synchronous events

The promise object is not put in any queue. It is when the timer expires that the queued event will be triggered as a new task, i.e. the resolve function will be called, which in turn will resolve the promise, which in turn will put entries in the microtask queue, one for each then callback and await-related effect. The microtask queue will be processed within the same (macro) task, before any other tasks are pulled from the task queue.

So there seems to be two event loops, am I right?

At least two; there can be more. For instance, in a browser context there may be yet another queue for events concerning DOM element mutation. See alse this answer about what some of the different specs say about this.

  1. Are these two statements equal?

Practically yes; Promise.resolve() is short for new Promise(r => r())

Why is the output sequence like the following?

setTimeout involves a task queue, while .then involves a microtask queue, which is always consumed before the task queue is processed; at least that is the consensus in current implementations.

Addendum

Here is some clarification for the sequence of events of the following code:

function timeout(ms){
    return new Promise((resolve, reject) => {
        setTimeout(resolve, ms, 'done');
    });
}

timeout(100).then((value) => {
    console.log(value);
});

First the code is parsed and a task is created to execute it:

Task 1

  • The function timeout is defined (a hoisted variable with the function as value)
  • timeout is called with argument 100
  • The promise object is created and the constructor callback is called synchronously. The two arguments are provided by the system. They are internal functions for us to call when the promise should resolve into either a fulfilled or rejected state (that is for us to decide).
  • setTimeout is called.
  • The arguments passed to setTimeout are used to create an entry in the internal timer list.
  • The timer's ID is returned, but your code does not use the return value.
  • The created promise object is returned to the caller of timeout
  • The caller calls the then method of this promise, passing it an anynomous function (the one with console.log)
  • The promise internal implementation stores this function in a queue (not the event queue) for potential later execution
  • The end of the code is reached, the callstack is empty
  • The micro task queue is checked for any entries, but it is also empty

In the background:

  • The timer expires and an entry is pushed to the task queue. It is a pending call to the function that was passed to setTimeout, i.e. resolve in our case. How exactly this happens is implementation dependent. The essence is that at some point the task queue has this task that can be consumed.

Task 2

  • The entry in the task queue (to call resolve) is found and processed
  • resolve is called
  • The promise internals (that implemented this resolve function) mark the promise as resolved
  • The promise internals create entries in the microtask queue: one for each then callback (or await) it had stored in its internal queue. In this case there is only one such entry: the anonymous function that was passed to the only then method in the code.
  • The call stack is empty (it was only a call to resolve)
  • The micro task queue is checked for any entries, and there is one:

Task 2, Micro task 1

  • The single entry in the micro task queue is consumed
  • The anonymous function is called
  • console.log is executed
  • The console implementation produces output
  • The callstack is empty
  • There are no more entries in the micro task queue

There are no more entries in the task queue. The system continues to check for new entries in the task queue.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Hi trincot, thanks a lot to your answer. I have been clear with question 1, 3 and 4, but still feel a little bit confused with question 2. What is the sequence of creating the `Promise` object, call the `setTimeout` function, resolve the `Promise` and run the `Promise then()'s` callback function? – Aurora Dec 25 '18 at 05:24
  • As you said above, is that `timeout` be called -> `Promise` be created -> `setTimeout` be called -> `Promise` be solved -> `Promise then()'s callback function` be called? – Aurora Dec 25 '18 at 05:31
  • Oooops, ask a little more about question 1. As you said, when the `promise` function is called, the `promise constructor callback` will be called. Did you mean the `promise constructor callback` will be called immediately instead of being pushed onto the macrotask (or microtask?) and waiting to be called later? Besides, I am confused about what `resolve` really mean, is that a function or something else? Will `resovle` be considered as a task? – Aurora Dec 25 '18 at 05:59
  • 1
    I added a section to my answer which I think will clarify all these points. – trincot Dec 25 '18 at 09:38
  • Thanks so much for your detailed explanation. Just one more question, is the `resolve` function a synchronous function? – Aurora Dec 25 '18 at 19:52
  • 1
    The function `resolve` is executed immediately when you call it (like any function), but elsewhere in your code you can only notice *asynchronously* that a promise is resolved, since `resolve` does not *synchronously* call the `then` callbacks that you might have. Instead `resolve` defers those calls in the microtask queue. I have described this process. – trincot Dec 25 '18 at 19:55
1
  1. No, function will not create a promise until you call it.
  2. The promise will be created when you call timeout(100), will be resolved on setTimeout. Not sure about details with event queue.
  3. Seems so (tried in the console).
  4. Certainly watch this Philip Roberts: What the heck is the event loop anyway? | JSConf EU minute 11:30 or so.
Valery Baranov
  • 368
  • 3
  • 10
  • 1
    Philip Roberts does not talk about microtasks in his video which are the cause of the "strange behavior" in the 4th example – lleon Dec 24 '18 at 23:25