0

Trying to understand promises.

Invoking .then() on a promise, returns a new promise. The two promises are different objects.

  let promise = new Promise(function(resolve, reject) {resolve('ok');});
  let obj2 = promise.then(
    function(result){/*do nothing*/},
    function(error){/*do nothing*/}
  );
  alert(obj2 instanceof Promise);           //true
  alert(promise === obj2);                  //false

I'm wondering where the executor for the 2nd promise is and what it's defaulted to. Certainly, trying to create a promise with no executor throws an error.

 let promise = new Promise(); //TypeError: undefined is not a function

The reason I want to know this is on account of the following code.

<html>
<head>
  <title>chained promises</title>
  <script type='module'>
    window.onload = function(){   
      new Promise(function(resolve, reject) {
        setTimeout(function(){
          (Math.random()>0.5) ? resolve('ok') : reject(new Error('something bad happened'))}, 
          1000
        );
      }).then(                       
        function(result){
         document.getElementById('p').innerHTML += `then #1 result handler: ${result}<br>`;
          return result;
        },
        function(error){
          document.getElementById('p').innerHTML  += `then #1 error handler: ${error}<br>`;
          return error;
        }
      ).then(                      
        function(result){
          document.getElementById('p').innerHTML += `then #2 result handler: ${result}<br>`;
        },
        function(error){
          document.getElementById('p').innerHTML  += `then #2 error handler: ${error}<br>`;
        }
      );
    }
  </script>
</head>
<body>
  <p id='p' class='result'></p>
</body>
</html>

output:

then #1 error handler: Error: something bad happened
then #2 result handler: Error: something bad happened

Half the time the promise is resolved, and it's otherwise rejected.

When the initial promise is rejected, as above, the error handler of the first .then is invoked. However, when the 2nd .then is reached, its the result handler that gets invoked. Since the 2nd .then is a method of the 2nd promise, I'm wondering where the executor for that second promise is.

Could anyone clarify?

twisted
  • 742
  • 2
  • 11
  • 19

2 Answers2

3

It is created within the native .then() method. It creates the new Promise and passes an executor to it, so that it gets the resolver functions to call when your callback completes.

Usually, all of this is native code, and it will not actually call the global Promise constructor but just directly instantiate a native promise, so the executor function gets optimised away as well. However, we can subclass Promise, where we will actually receive an executor function when the then method is called:

class LogPromise extends Promise {
  constructor(executor, orig = "From .then():") {
    console.log(orig, executor);
    super(executor);
  }
}
const p = new LogPromise(() => {}, "From explcit call:");
p.then();
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • While this is clever, I'm not sure that this is a helpful answer for the OP's question. First off, it's not obvious to the reader that `.then()` instantiates a new instance of LogPromise in this case, and secondly why reinvent the wheel here? The usage of this code would be wildly different than their original code, and still doesn't answer why their error callback 1 isn't invoking their error callback 2. – mhodges Jan 26 '21 at 22:40
  • @mhodges Yeah, I just answered the title question, and only later realised that they had a very different problem (which is probably a duplicate of [this question](https://stackoverflow.com/questions/16371129/chained-promises-not-passing-on-rejection)) :-/ – Bergi Jan 26 '21 at 22:47
  • Ah, yeah that makes sense. And I agree it's probably a duplicate, although they are asking about the native `Promise` API rather than some deferred library before native Promises were a thing, the mechanics should be roughly the same, though. – mhodges Jan 26 '21 at 22:52
2

Essentially each promise that's created from each then() gets resolved/rejected automatically based on the return status of the callback function. If you reject the promise by either returning a rejected promise (return Promise.reject(err)), throwing an error, or if an error occurs, it will call the 2nd callback (error handler) of the next .then(). So, if you want to chain promises and keep calling the error handler, you need to keep rejecting the promise. Returning a valid value will resolve the first promise, which is why your "success" callback gets called in the 2nd .then()

You can also manually return a Promise() from .then() and have it immediately resolve/reject based on the logic.. promises don't have to have async logic inside of them

function testPromise(isSuccess) {
  new Promise((resolve, reject) => {
    setTimeout(() => {
      if (isSuccess) { resolve("Success!"); }
      else { reject(new Error("NOT SUCCESS!")); }
    }, 1000)
  }).then((res) => {
    console.log("Inside 1st .then success:", res);
    return res;
  }, (err) => {
    console.log("Inside 1st .then error:", err.message);
    // return Promise.reject(err); // this also works to reach next error handler
    // throw err; // this also works to reach next error handler
    return new Promise((resolve, reject) => reject(err));
  }).then((res) => {
    console.log("Inside 2nd .then success:", res);
  }, (err) => {
    console.log("Inside 2nd .then error:", err.message);
  })
}

testPromise(true);
testPromise(false);
mhodges
  • 10,938
  • 2
  • 28
  • 46