2

i have question about Promises on something that is really confusing me.

The .then() method

before i will get into the thing that confusing me about the .then() method i would do a brief explanation on how the Javascript Engine works based on my knowledge.

From what i know Javascript is not a asynchronous but rather a synchronous language

The Javascript Engine works synchronously but the Javascript Engine isn't the only thing that run on the Browser

There are things like the Rendering Engine,setTimeout,HTTP Request etc enter image description here

And the Javascript Engine can talk to them when we are calling a native functions for example setTimeout so the setTimeout function will call a program outside of the Javascript Engine for timing enter image description here

And of course when the timer will end it will send the callback to the event queue and only after the Javascript finished all the Executions context only then it will look at the event queue

Alright now let's move to Promises My question is how .then() knows when resolve() was called I have read articles and they say that .then() works asynchronously which is sounds weird to me because Javascript is synchronous doesn't it ? maybe i didn't understood them correctly

so i made my own assumptions on how .then() works because i haven't found a source that gave me the feeling and confidence that i know exactly how .then() works.

one of them(my assumptions) was that there are two stages in the .then method

i will use this code for demonstration to explain my assumption

var myPromise = new Promise(function(resolve){
                    resolve('Hello Stackoverflow !');
                 });
                 
                 myPromise.then(function(result){
                    console.log(result);
                 });

so based on my assumption the resolve('Hello Stackoverflow !') function calls the .then method and the .then check two things here are the following

1.if the callback parameter of .then() was created

2.if the Promise status is set to resolved

and if both conditions are true then the .then() method will insert the callback with the value Hello Stackoverflow ! to the event queue and only after all the execution context popped of the stack then it will run the callback and we will get the result Hello Stackoverflow !

again this is based only on my assumption maybe i'm totally wrong about that.

So if you examine what i just said you can reach a conclusion that the .then method is called twice Why ?

the first time is when the resolve function called it but not all conditions were true the one that isn't true is that the .then callback parameter was created but it wasn't created yet because we haven't got to the line of code that we created the callback so the condition is false

and the second time is when we called the .then method and created the callback and all conditions were now true so it will insert the callback to the event queue and after all the execution contexts will popped of the stack then the callback will be called and we will get Hello Stackoverflow !

hope you understand what i tried to explain

Am i right or wrong ?

Thanks in advance for all of you :)

AbsoluteBeginner
  • 2,104
  • 3
  • 10
  • 21
  • 2
    The `.then()` method is called immediately after `myPromise` is initialized. The `.then()` method and the *callback* passed to `.then()` are two completely different things. – Pointy Apr 03 '21 at 18:08
  • 1
    Callbacks scheduled using Promises are not added to the task queue - they are [added to the micro-task queue](https://stackoverflow.com/questions/66387109/javascript-async-callbacks-promise-and-settimeout()). – Yousaf Apr 03 '21 at 18:10
  • 1
    "*if both conditions are true [it] will insert the callback with the value to the event queue*" - yes. But if the promise is not fulfilled yet, the `then` method just stores the callback in an internal list, so that it can be put in the event queue by the `resolve` call. – Bergi Apr 03 '21 at 19:15
  • may I suggest reading answer on same topic. – Syed Apr 04 '21 at 12:20
  • for some odd reasons we seem to miss the concept of WEB API that is now part of Js engine here is very good article as whats happening behind the scene through web API promise is part of thread pool for web API which makes asynchronous like operative https://dev.to/steelvoltage/if-javascript-is-single-threaded-how-is-it-asynchronous-56gd – Syed Apr 04 '21 at 12:27
  • @CaptainMagmelatonCap, you received a few answers. Any feedback? – trincot Apr 05 '21 at 07:29

2 Answers2

2

you can reach a conclusion that the .then method is called twice

This is not true. The then method is called just like any method is called: when the statement/expression having that method call is evaluated in the normal execution flow.

The order of synchronous execution of your example is as follows:

  1. The callback function function(resolve) {...} is passed to the Promise constructor function.

  2. This constructor immediately executes the callback it receives as argument, passing it a resolve and reject argument.

  3. resolve is called, passing it the string.

  4. The promise implementation, that implemented the resolve function, is therefore notified and sets the state of the promise object to fulfilled, and registers the value it is fulfilled with (the string). It also puts a job on a Promise Job queue.

  5. The promise constructor finishes execution and returns the promise instance, which is assigned to var myPromise

  6. The callback function function(result) {...} is passed to the myPromise.then() method.

  7. The native then implementation registers the callback, but does not execute it.

  8. The native then function finishes execution and returns a new promise. Your script does not capture this return value, so let's just label this new promise as promiseB.

  9. The scripts ends, leaving the call stack empty.

Now we get to what is commonly called the asynchronous execution part, which always starts with an entry in a job/event queue:

  1. The host will check which job queues have entries, giving precedence to job queues that have a high priority. A Promise job queue has a very high priority, typically higher than the event queue that deals with user interaction or other external events. So, the job that was put in the queue at step 4 above, is taken out of the Promise Job queue.

  2. The above job executes.

  3. This job will (one-by-one) call the callback functions that have been registered as then callback (such as the one registered in step 7) on the myPromise object. NB: I am ignoring the async/await syntax here, to keep it simple.

  4. So in this case the only then-callback in your script is executed -- not to be confused with the then method itself (which was already executed in step 6). The then-callback is executed with as argument the value that the promise myPromise was fulfilled with (the string that was registered in step 4).

  5. console.log is executed.

  6. the then callback finishes execution and returns undefined.

  7. promiseB is fulfilled with this return value (undefined in this case).

  8. The job execution finishes. The call stack is empty again.

trincot
  • 263,463
  • 30
  • 215
  • 251
  • Finally, got it, could you improve your answer with a second example using the async/await syntax? – shellwhale Jan 16 '22 at 16:50
  • 1
    @shellwhale, like I wrote on my answer, I didn't want to go there, so to focus on the question asked here. If you have a doubt about `async/await` and can't find an answer on this site, feel free to post, and I will be glad to have a look at it. – trincot Jan 16 '22 at 16:59
0

A very basic barebone implementation of Promise, which may answer your question:

class Promise {
  constructor(fn) {
    this.status = "PENDING";
    this.result = null;
    this.successCB = [];
    this.errorCB = [];
    fn(this.resolve, this.reject); // This actually goes into microtask
  }

  resolve = data => {
    this.status = "SUCCESS";
    this.result = data;
    this.successCB.forEach(eachCB => eachCB(data));
    this.successCB = [];
  };

  reject = error => {
    this.status = "FAILED";
    this.result = error;
    this.errorCB.forEach(eachCB => eachCB(error));
    this.errorCB = [];
  };

  then = (successCB, errorCB) => {
    switch (this.status) {
      case "PENDING":
        this.successCB.push(successCB);
        this.errorCB.push(errorCB);
        break;
      case "SUCCESS":
        successCB(this.result);
        break;
      case "FAILED":
        errorCB(this.result);
        break;
      default:
        break;
    }
  };
}

To keep it simple I haven't considered chaining promises or advanced error handling. But this should work when then is executed before/after resolve was complete.

Easwar
  • 4,379
  • 1
  • 9
  • 21
  • the "*`// This actually goes into microtask`*" comment is on the wrong line. It should be on the `forEach`s and on the calls in the settled `case`s. – Bergi Apr 03 '21 at 19:17
  • The function executes during promise creation, irrespective of `then`. Hence its delegated to microtask within the contructor. – Easwar Apr 03 '21 at 19:23
  • 1
    No, it's not a microtask, the executor callback is called synchronously from within the constructor. Microtasks are only involved in the handlers passed to `then`. – Bergi Apr 03 '21 at 19:24
  • I didn't know that. Thanks ! – Easwar Apr 03 '21 at 19:31
  • This calls the callbacks synchronously. Secondly, it allows the promise state to switch from fulfilled to rejected and vice versa. That is not how it's supposed to work. – trincot Jan 16 '22 at 17:05