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?
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.
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 .
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With