Search code examples
javascriptpromisesettimeoutes6-promise

Understanding setTimeout and its implementation in JavaScript Promises


I have the following code:

function MyPromise(configFunction) {
  let nextSuccessCallBack = undefined;
  let nextResolve = undefined;
  configFunction(function(message){
    setTimeout(function(){
      if(nextSuccessCallBack) {
        var result = nextSuccessCallBack(message);
        if(result && result.then) {
          result.then(nextResolve);
        } else {
          nextResolve && nextResolve(result);
        }
      }
    })
  });

  return {
    then: function(successCallback) {
      nextSuccessCallBack = successCallback;
      return new MyPromise(function(resolve) {
        nextResolve = resolve;
      })
    }
  }
}

new MyPromise(function(resolve, reject) {
  resolve('new message');
}).then(function(message) {
  console.log(message);
  return 'another message'
}).then(function(message) {
  console.log(message)
  console.log('here')
})

In this example, it seems that the nextSuccessCallBack is set to be the callback functions within .then, then its value within the setTimeout function is now populated. However, this confuses me. I thought that when we reach the return statement in the constructor, we return the object and effectively stop the function? If that the case then how does the program even get to the setTimeout?


Solution

  • This is not a correct Promise implementation. It clearly has no capabilities for rejections, but also for the implemented fulfilment feature it does not comply with the Promises/A+ specification. Just to give two examples:

    1. It does not comply with rule 2.1.2.2

      When fulfilled, a promise must have a value, which must not change.

      ....nor with rule 2.2.2.3:

      If onFulfilled is a function it must not be called more than once.

      In your implementation, if you would add a second call to resolve:

      new MyPromise(function(resolve, reject) {
        resolve('new message'); resolve('new message2');
      }).then((function(message) {
        console.log(message);
        // ... etc
      })
      

      ...then both calls to resolve would fire the then callback, and show that the promised value was modified after the first time it was set. This is in complete violation of the principle of promises: promises can only resolve once.

    2. It does not comply with rule 2.2.6:

      then may be called multiple times on the same promise.

      • If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.

      This does not happen if we use the following code:

      let p = new MyPromise(resolve => resolve("value"));
      p.then(console.log); // This callback is called
      p.then(console.log); // This callback is not called -> violation!
      

    These are basic shortcomings, and this is just the tip of the iceberg.

    If you are interested in how it could be implemented in compliance with Promise/A+, then have a look at one I did a few years ago with a step-by-step explanation.

    As to your question

    how does the program even get to the setTimeout?

    When your main code executes this:

    new MyPromise(function(resolve, reject) {
      resolve('new message');
    })
    

    ...then the parameter variable configFunction is initialised with that callback function. The MyPromise constructor calls it, passing it the following callback as first argument:

      function(message){
        setTimeout(function(){
          if(nextSuccessCallBack) {
            var result = nextSuccessCallBack(message);
            if(result && result.then) {
              result.then(nextResolve);
            } else {
              nextResolve && nextResolve(result);
            }
          }
        })
      }
    

    So that means that resolve in your main code's callback function is initialised with the above callback function. Then your main code's callback function calls resolve with a string argument. Since resolve is the above callback function, we can see it gets executed with message initialised to "new message". This function executes setTimeout.