Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How promise chains reference the next error handler

I am wondering if anyone knows how promise chains reference the next error handler - for example:

  const p = new Promise(resolve => resolve(5))
   .then(v => 5*v)
   .then(v => { throw 'foo' });

  p.then(v => v/4)
   .then(v => v+3)
   .catch(e => console.error('first catch:', e));

  p.then(v => v/4)
   .then(v => v+3)
   .catch(e => console.error('second catch:', e));

If you run that you will get:

first catch: foo
second catch: foo

to my knowledge, every time Promise.prototype.then is a called, a new promise is created and returned. The only way I can think of for the promise to be able to find the next error handler for each chain, is to have an array of references to the children. So if a Promise is rejected, it would go through all the children and find the closest rejection handlers in each child chain.

Does anyone know how this is implemented?

like image 314
Alexander Mills Avatar asked Jun 16 '18 02:06

Alexander Mills


People also ask

How does promise all handle errors?

Promise. all is all or nothing. It resolves once all promises in the array resolve, or reject as soon as one of them rejects. In other words, it either resolves with an array of all resolved values, or rejects with a single error.

What happens when a promise throws an error?

The same thing is possible for promises. If we throw inside . catch , then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the next closest successful .

How do you handle rejection promises?

We must always add a catch() , otherwise promises will silently fail. In this case, if thePromise is rejected, the execution jumps directly to the catch() method. You can add the catch() method in the middle of two then() methods, but you will not be able to break the chain when something bad happens.


2 Answers

The way I see it:

Think of promises as nested arrays:

[then1,[then1.1, then1.2, catch1.1, then1.3, then1.4], then2, catch1]

looks like this:

new Promise(...)
.then(() => {         // <- 1
  return Promise(...)
          .then()     // <- 1.1
          .then()     // <- 1.2
          .catch()    // <- 1.1
          .then()     // <- 1.3
          .then()     // <- 1.4
})
.then()               // <- 2
.catch()              // <- 1

Now imagine we start execution and it begins in the topmost array. We have an index for each array specifying which element we are waiting for in terms of execution. We start off by calling .then1, which itself returns a promise chain (another array). When an error occurs your lowest in hierarchy array (the deepest one) skips through (doesn't execute) it's elements until it finds a catch. If it does, it executes the catch and continues executing the other elements. If it doesn't find a catch it asks the parent array to find and execute a catch, skipping all elements including its children arrays because they are not catch.

In our example if an error occurs in then1.2 it will be caught by catch1.1, but if it occurs in then1.3 it will propagate all the way to catch1 skipping then1.4 and then2

Edit:

Here is code to experiment with:

new Promise(res => res())
.then(() => 
    new Promise(res => res())
    .then(() => console.log(1))
    .then(() => {console.log(2); throw "Error 1"})
    .catch((err) => console.log(err))
    .then(() => {console.log(3); throw "Error 2"}))
    .then(() => console.log(4))
.then(() => 
    new Promise(res => res())
    .then(() => console.log(6))
    .then(() => {console.log(7); throw "Error 3"})
    .catch((err) => console.log(err))
    .then(() => console.log(8))
    .then(() => {console.log(9); throw "Error 4"}))
.then(() => console.log(10))
.catch((err) => console.log(err))

it logs:

1

2

Error 1

3

Error 2

like image 165
Alexander Vitanov Avatar answered Oct 13 '22 15:10

Alexander Vitanov


So let's just step through your example first.

const p = new Promise(resolve => resolve(5))
 .then(v => 5*v)
 .then(v => { throw 'foo' });

p.then(v => v/4)
 .then(v => v+3)
 .catch(e => console.error('first catch:', e));

p.then(v => v/4)
 .then(v => v+3)
 .catch(e => console.error('second catch:', e));

Here you actually have 3 different promises:

p which is Promise.then.then, anonymous which is p.then.then.catch, and anonymous which is p.then.then.catch.

Remember that Promise.then and Promise.catch return Promises that encompass the Promise from before. So what you're really ending up with is nested promises with those three promise chains.

When p eventually throws in the third promise at evaluation the other two promises are now checking the return type, flags, and other internal information that eventually leads to them realizing a promise from earlier in the chain rejected and thus it must reject too. You can also emulate this with the following:

var p = Promise.reject(new Error('Hi from p!'))

p.catch(e => console.log('Hello from a different promise', p))
==
p.catch(e => console.log('Hello from a yet different promise', p))

You should notice that the return of the evaluation is false which means the two objects aren't equal.

Now what happens if we wait a bit and then attach a new catch handler to p?

setTimeout(() => p.catch(console.log), 500)

You should notice another console log, this time with only 'Hi from p!'

And the reason is order at which a Promise evaluates. It evaluates on creation and if it's created on a rejected promise, it evaluates to rejected and goes to the catch handler.

What do you think you'll get with the following?

Promise
  .reject(new Error('1'))
  .catch(console.log)
  .then(() => Promise.reject(new Error('2')))
  .then(() => new Error('3'))
  .then(console.log)
  .catch(console.log)
  .catch(() => console.log('4'))

That's right you'll see a printed Error('1'), then a printed Error('2'), but not a printed Error('3') or a printed '4'. That's because a .catch promise does one of two things: resolves the promise to the resolved value, or resolves the promise to the functions value if the chain is rejected.

In contrast .then only resolves a promise. If it's resolution is rejected, it rejects with the resolution.

It's a little confusing yes, but once you realize that promise will wait for resolution and will check and chaining promises it becomes a little easier to figure out where you'll reject to.

like image 26
Robert Mennell Avatar answered Oct 13 '22 17:10

Robert Mennell