4

There are two cases, both return Promise and are chained with the following then methods. But the result sequence is different. Some notes: Promise.resolve(value) -> returns immediately fulfilled Promise with the value. And in the then method when we return value again it returns fulfilled Promise with the value. Logically there shouldn't be any difference. Both are immediate... Thanks in advance...

Promise.resolve(1)
  .then((v) => {
    console.log(v);
    return Promise.resolve(v + 1);
  })
  .then((v) => {
    console.log(v);
    return Promise.resolve(v + 1);
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  });

Promise.resolve(10)
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  });

//Result on the console:
//1
//10
//11
//12
//2
//13
//3
//4

//

Promise.resolve(1)
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  });

Promise.resolve(10)
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  })
  .then((v) => {
    console.log(v);
    return v + 1;
  });

//Result on the console:
//1
//10
//2
//11
//3
//12
//4
//13
  • 1
    The other of execution of asynchronous tasks is more or less undefined for practical purposes. (You *can* go into the details of microtasks and event loops, but why would you?) As long as each promise returns the expected result, you shouldn't care too much about *when* it got there. – deceze Oct 03 '19 at 10:56
  • @deceze thank you. I just want to undestand the logic behind it ) – sharpenedguy Oct 03 '19 at 11:52

2 Answers2

4

There is an important difference: Remember that then returns a promise (let's call it promise T). When you do

return Promise.resolve(v + 1);

...you're resolving promise T to a promise (let's call it promise B). But when you do:

return v + 1;

...you're resolving promise T to an immediate value.

Resolving promise T to promise B introduces an extra "tick" in the resolution cycle. Instead of promise T queuing a call to its resolution handler, it has to wait until promise B calls the resolution handler T has to set on it, and then call its resolution handler. Hence the extra "tick" (basically, another cycle of the microtask queue).

Simpler example, note that it logs "Second 2" *before "First 2":

Promise.resolve(1)
    .then(v => Promise.resolve(v + 1))
    .then(v => console.log(`First: ${v}`));
Promise.resolve(1)
    .then(v => v + 1)
    .then(v => console.log(`Second: ${v}`));

...whereas if you don't have the extra promise, it logs "First 2" before "Second 2":

Promise.resolve(1)
    .then(v => v + 1)
    .then(v => console.log(`First: ${v}`));
Promise.resolve(1)
    .then(v => v + 1)
    .then(v => console.log(`Second: ${v}`));

This extra tick was recently removed for async functions that do things like return await somePromise (instead of just return somePromise) because it can reliably be removed for native promises handled internally via async/await. But I'm not sure it can be reliably removed for your case, and/or whether doing so will receive any significant attention from those who'd have to do it. It would require treating a native promise returned from a then handler different from any other thenable¹, which could be problematic. (But I don't know for sure that it would be.)


¹ thenable vs. promise: https://promisesaplus.com

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • While this is technically true, I don't think of any of the involved parties meant for this to be the case and this is a coincidence. People mostly didn't want zalgo and code having different execution order based on sync/async. The fact Promise.resolve works this way is just because we didn't want a second function (Promise.cast https://esdiscuss.org/topic/promise-cast-and-promise-resolve ) and it ended up doing both `map` and `flatMap`. – Benjamin Gruenbaum Oct 03 '19 at 11:08
  • @BenjaminGruenbaum - I'm not speaking to intent, and can't. :-) (Although "coincidence" seems like a mischaracterization; everyone significantly involved would be aware of the difference between `return v + 1` and `return Promise.resolve(v + 1)` from a handler with the semantics defined by the spec.) I'm just speaking to what explains the behavior the OP sees. – T.J. Crowder Oct 03 '19 at 11:14
  • 1
    That was just because it was easier to specify probably. I remember a discussion with Benedikt, Maya and Daniel at some collab summit (Berlin 2018?) when Benedikt showed me the 4 promises thing and I was like "huh, there is no way that was intentional - instead of optimising it we should change the spec". I think this might be one of those cases where it just happens to wait an extra microtick which is unneeded. – Benjamin Gruenbaum Oct 03 '19 at 11:21
  • 1
    Also - I do not disagree - I am just amused :D This was never intended as a difference, `return foo` should implicitly be `return Promise.resolve(foo)` and the timing difference (and strict microtask scheduling) is an artifact of the ECMAScript spec - the Promises/A+ spec made no such requirements. It only required the execution context stack contains only platform code. – Benjamin Gruenbaum Oct 03 '19 at 11:25
  • I always thought that Promise.resolve(3) and Promise.resolve(Promise.resolve(Promise.resolve(3))) act the same. Because both synchronously return the resolved Promise. And I console.log it, it confirm this kind of misknowledge as I got it. When the state of the Promise is changed the handlers are coming through the microtask queue. But what if the states are changing synchronously and there is no handler to go through the queue wouldn't that make the both scenario equal. So with this logic both reach the next then method with the order of they are declared. Thank you. – sharpenedguy Oct 03 '19 at 11:46
  • @T.J. Crowder I couldn't see B's resolution handler in your terminology :( Thank you. – sharpenedguy Oct 03 '19 at 11:47
  • And if you can share a source that under what conditions it needs a tick, that would be great. – sharpenedguy Oct 03 '19 at 12:13
  • @sharpenedguy - The handler attached to B is internal to `then`. I don't understand what you mean by "share a source," unless you're referring to the specification. If so, it's in [this section](https://tc39.es/ecma262/#sec-promise-objects). I don't have time to go digging deeply, but it's probably primarily [here](https://tc39.es/ecma262/#sec-promiseresolvethenablejob). – T.J. Crowder Oct 03 '19 at 12:27
  • @T.J. Crowder thank you. Please correct me if I'm wrong. Promise.resolve(3) and Promise.resolve(Promise.resolve(Promise.resolve(3))) are not the same under the hood. They are going through different processes. Promise.resolve(3) goes through 1 cycle for being resolved. And Promise.resolve(Promise.resolve(Promise.resolve(3))) is going through 3 cycles(ticks) for being resolved. If these are through why they are thought or taught as immediately resolved Promises and when we console.log them we are getting immediately desired return. – sharpenedguy Oct 03 '19 at 12:36
  • 1
    @sharpenedguy `Promise.resolve(Promise.resolve(1))` is a slightly different case from the above and doesn't add an extra layer, because it has an optimization in it: When you pass a promise into `Promise.resolve`, the [PromiseResolve](https://tc39.es/ecma262/#sec-promise-resolve) abstract operation checks to see if that promise's constructor is the same one as the promise you called `resolve` on. If so, it just returns the promise you passed in, making the outer call a complete non-operation. Which means `const p = Promise.resolve(1); console.log(p === Promise.resolve(p));` logs `true`. – T.J. Crowder Oct 03 '19 at 13:07
  • @sharpenedguy So if you were taught that they're the same thing, that's fundamentally correct -- for `Promise.resolve`. :-) I'm afraid I have to defer any further follow-on questions to the spec; I have to get back to work. :-) – T.J. Crowder Oct 03 '19 at 13:09
0

Remember that javascript is one threaded, and will add all methods to a queue. What I done below is how the promises, in a way, should be written.

Javascript adds all the tasks in the first column, goes through the queue to execute all commands, and then adds more tasks in column 2 and 3 as they are found in the asynchronous methods.

It's pretty logical if you look at it like this.

enter image description here

Rickard Elimää
  • 7,107
  • 3
  • 14
  • 30
  • "*Javascript adds all the tasks in the first column*" - no it doesn't. `then` attaches a handler to a promise - synchronously - but the handlers are only scheduled on the queue when the promise settles. – Bergi Nov 27 '21 at 05:33