Marcus Lee
Marcus Lee

Reputation: 435

Confused about the Promises

What does resolve actually do?
Consider the code below. It prints : 1 3 4 5 6 9 7 10 11 2.
Irrespective of where resolve is written, it prints the same!
Can someone explain why this happens?

    new Promise((resolve, reject) => {
        console.log(1);
        setTimeout(() => {
            console.log(2);
        }, 0);
        resolve();
        new Promise((resolve, reject) => {
            console.log(3);
            resolve();
        })
        .then(() => {
            console.log(4);
        })
        .then(() => {
            console.log(9);
        })
        .then(() => {
            console.log(10);
        })
        .then(() => {
            console.log(11);
        })
        ;
        // resolve()
    }).then(() => {
        console.log(5);
        new Promise((resolve, reject) => {
            console.log(6);
            resolve();
        }).then(() => {
            console.log(7);
        });
    })

Upvotes: 0

Views: 622

Answers (2)

jfriend00
jfriend00

Reputation: 707158

What does 'resolve' actually do?

Calling resolve(x) does three things.

  1. It changes the internal state of the promise to fulfilled. Once the state is changed to fulfilled, the promise state cannot be changed again. This is a one-way, permanent change.

  2. It sets the value x (whatever single argument you pass to resolve) as the resolved value of the promise (this is stored internal to the promise). If nothing is passed to resolve(), then the resolved value is undefined.

  3. It inserts an event into the event queue to trigger the .then() handlers of this current promise to get called upon an upcoming cycle through the event loop. This schedules the .then() handlers to run after the current thread of Javascript execution finishes.

I'll explain the sequence you see in the console, but first here are a few points that will help to understand this:

  • The promise executor function (the callback passed to new Promise(fn)) is called synchronously (in the midst of the current thread of execution).
  • When a setTimeout() timers fires (internal to the JS engine), the timer callback is inserted in the event queue and will be picked up on a future cycle of the event loop.
  • When a promise resolves, an event is inserted into the event queue and will be picked up on a future cycle of the event loop.
  • There are multiple types of event queues in the event loop and not all have the same priority. In general promise events will be picked up before most other types of events though it's possible this can vary a bit according to the implementation. So, when multiple types of events are both put in the event queue so they are in there at the same time, this can affect which one gets called first.
  • Multiple .then() or .catch() handlers that are added to the event queue are handled in the order (relative to each other) that they were originally triggered in a FIFO basis (first-in-first-out).
  • When promise chaining with something like fn().then(f1).then(f2).then(f3) keep in mind that each .then() returns a new promise that will have its own time that it gets resolved or rejected, after the one before it and depending upon what happens in its handler function.

So, here's the sequence of events in your code:

  1. The first promise executor function is called, thus you get the output 1
  2. A timer is created with a timeout of 0. At some point very soon, a timer callback event will be added to the event queue.
  3. You call resolve() on that first promise. This inserts an event/task into the promise queue to call its .then() handlers on a future cycle of the event loop. The rest of this sequence of Javascript code continues to execute. But, notice that there are not yet any .then() handlers on that first promise as its chained .then() methods haven't yet been executed.
  4. You create a second promise and its executor function is called immediately which outputs 3.
  5. You call resolve() on that second promise. This inserts an event/task into the promise queue to call its .then() handlers on a future cycle of the event loop. The rest of this sequence of Javascript code continues to execute.
  6. .then() is called on that second promise. This registers a .then() handler callback function in that second promise (adds it to an internal list) and returns a new promise.
  7. .then() is called on that newly returned promise (third promise). This registers a .then() handler callback function in that third promise (adds it to an internal list) and returns a new promise.
  8. .then() is called on that newly returned promise (fourth promise). This registers a .then() handler callback function in that fourth promise (adds it to an internal list) and returns a new promise.
  9. .then() is called on that newly returned promise (fifth promise). This registers a .then() handler callback function in that fifth promise (adds it to an internal list) and returns a new promise.
  10. The executor function from the very first promise finally returns.
  11. .then() is called on the very first promise. This registers a .then() handler callback function in that first promise (adds it to an internal list) and returns a new promise.
  12. Because the .then() handler from the second promise ran before the .then() handler from the first promise, it gets put into the tasks queue first and thus you get the output 4 next.
  13. When this .then() handler runs, it resolves the promise that it created earlier, the third promise and it adds a task to the promise queue to run its .then() handlers.
  14. Now, the next item in the task queue is the .then() handler from the first promise so it gets a chance to run and you see the output 5.
  15. This then creates another new promise with new Promise(...) and runs its executor function. This causes the output 6 to show.
  16. This new promise is resolved with resolve().
  17. Its .then() is called which registers a .then() callback and returns a new promise.
  18. The current sequence of Javascript is done so it goes back to the event loop for the next event. The next thing that was scheduled was the .then() handler for the fourth promise so it gets pulled from the event queue and you see the output 9.
  19. Running this .then() handler resolved the fifth promise and inserts its .then() handler into the promise task queue.
  20. Back to the event queue for the next promise event. There we get the .then() handler from the final new Promise().then() in the code and you get the output 7.
  21. The above process repeats and you see the outputs 11, then 12.
  22. Finally, the promise task queue is empty so the event loop looks for other types of events that aren't as high a priority and finds the setTimeout() event and calls its callback and you finally get the output 2.

So, setTimeout() goes last here for a couple of reasons.

  1. Promise events are run before timer events (in ES6), so any queued promise events are served before any queued timer event.
  2. Because all of your promises resolve without actually having to wait for any other asynchronous events to complete (which is kind of not real-world behavior and not typically how or why one uses promises), the timer has to wait until they're all done before it gets its chance to run.

And, a few other comments:

  1. Figuring out the relative firing order of various .then() handlers in different and independent promise chains is sometimes possible (it's only possible here because there are no real asynchronous promise resolves with uncertain resolution times), but if you really need a specific execution order, then it's better to just chain your operations to explicitly specify the order you want things to run in the code. This removes any dependency on minute implementation details of the local Javascript engine and makes the code a ton more self-explanatory. In other words, someone reading your code doesn't have to go through the 22 steps I listed to follow the desired execution order. Instead, the code will just specify the order by direct promise chaining.

  2. In real code, it's unusual to have the orphaned, disconnected promise chains you create inside .then() handlers. Because you are not returning those promise from the .then() handler and thus inserting them in the parent promise chain, there is no way to communicate results or errors back from those disconnected promises chains. While there is very occasionally a reason to code a fire-and-forget operation that doesn't need to communicate at all with the outside world, that is unusual and is usually a sign of problem code that doesn't properly propagate errors and whose results aren't properly synchronized with the rest of what's going on.

when I place 'resolve' behind, it prints the same!

As you've discovered, this doesn't really change anything. The .then() following the new Promise(...) isn't executed until after the executor function finishes running and returns so it doesn't really matter where inside the executor you call resolve(). Said another way, none of the .then() handlers can even be registered until after the promise executor returns so no matter where you call resolve() in the promise executor, the result is the same.

Upvotes: 3

random
random

Reputation: 7891

resolve indicate the completion of asynchronous task.

In the below code,

new Promise((resolve, reject) => {
    console.log(1);
    setTimeout(() => {
        console.log(2);
    }, 0);
    resolve();
    new Promise((resolve, reject) => {
        console.log(3);
        resolve();
    })
    .then(() => {
        console.log(4);
    })

You have created new Promise, and immediately resolved it using resolve(), so it does not wait for setTimeout to get executed. The resolve(); is immediately followed by new Promise, which creates new Promise followed by the execution of immediate then section.

In .then you have not returned any thing, so your then's are not chained properly. Return the value in then to chain it properly.

new Promise((resolve) => {
    console.log("1");
    setTimeout(() => {
        resolve(2);
    });
}).then((val) => {
    console.log(val);
    return "3";
}).then((val) => {
    console.log(val);
    return "4";
}).then((val) => {
    console.log(val);
});

Upvotes: 2

Related Questions