I have trouble understanding the difference between putting .catch BEFORE and AFTER then in a nested promise.
Alternative 1:
test1Async(10).then((res) => {
return test2Async(22)
.then((res) => {
return test3Async(100);
}).catch((err) => {
throw "ERROR AFTER THEN";
});
}).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
});
Alternative 2:
test1Async(10).then((res) => {
return test2Async(22)
.catch((err) => {
throw "ERROR BEFORE THEN";
})
.then((res) => {
return test3Async(100);
});
}).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
});
The behavior of each function is as follow, test1 fail if number is <0 test2 fails if number is > 10 and test3 fails if number is not 100. In this case test2 is only failing.
I tried to run and make test2Async fail, both BEFORE and AFTER then behaves the same way and that is not executing the test3Async. Can somebody explain to me the main difference for placing catch in different places?
In each function I console.log('Running test X') in order to check if it gets executed.
This question arises because of the previous thread I posted How to turn nested callback into promise?. I figure it is a different problem and worth posting another topic.
So, basically you're asking what is the difference between these two (where p is a promise created from some previous code):
return p.then(...).catch(...);
and
return p.catch(...).then(...);
There are differences either when p resolves or rejects, but whether those differences matter or not depends upon what the code inside the .then() or .catch() handlers does.
What happens when p resolves:
In the first scheme, when p resolves, the .then() handler is called. If that .then() handler either returns a value or another promise that eventually resolves, then the .catch() handler is skipped. But, if the .then() handler either throws or returns a promise that eventually rejects, then the .catch() handler will execute for both a reject in the original promise p, but also an error that occurs in the .then() handler.
In the second scheme, when p resolves, the .then() handler is called. If that .then() handler either throws or returns a promise that eventually rejects, then the .catch() handler cannot catch that because it is before it in the chain.
So, that's difference #1. If the .catch() handler is AFTER, then it can also catch errors inside the .then() handler.
What happens when p rejects:
Now, in the first scheme, if the promise p rejects, then the .then() handler is skipped and the .catch() handler will be called as you would expect. What you do in the .catch() handler determines what is returned as the final result. If you just return a value from the .catch() handler or return a promise that eventually resolves, then the promise chain switches to the resolved state because you "handled" the error and returned normally. If you throw or return a rejected promise in the .catch() handler, then the returned promise stays rejected.
In the second scheme, if the promise p rejects, then the .catch() handler is called. If you return a normal value or a promise that eventually resolves from the .catch() handler (thus "handling" the error), then the promise chain switches to the resolved state and the .then() handler after the .catch() will be called.
So that's difference #2. If the .catch() handler is BEFORE, then it can handle the error and allow the .then() handler to still get called.
When to use which:
Use the first scheme if you want just one .catch() handler that can catch errors in either the original promise p or in the .then() handler and a reject from p should skip the .then() handler.
Use the second scheme if you want to be able to catch errors in the original promise p and maybe (depending upon conditions), allow the promise chain to continue as resolved, thus executing the .then() handler.
The other option
There's one other option to use both callbacks that you can pass to .then() as in:
p.then(fn1, fn2)
This guarantees that only one of fn1 or fn2 will ever be called. If p resolves, then fn1 will be called. If p rejects, then fn2 will be called. No change of outcome in fn1 can ever make fn2 get called or vice versa. So, if you want to make absolutely sure that only one of your two handlers is called regardless of what happens in the handlers themselves then you can use p.then(fn1, fn2).
jfriend00's answer is excellent, but I thought it would be a good idea to add the analogous synchronous code.
return p.then(...).catch(...);
is similar to the synchronous:
try {
iMightThrow() // like `p`
then()
} catch (err) {
handleCatch()
}
If iMightThrow() doesn't throw, then() will be called. If it does throw (or if then() itself throws), then handleCatch() will be called. Notice how the catch block has no control over whether or not then is called.
On the other hand,
return p.catch(...).then(...);
is similar to the synchronous:
try {
iMightThrow()
} catch (err) {
handleCatch()
}
then()
In this case, if iMightThrow() doesn't throw, then then() will execute. If it does throw, then it would be up to handleCatch() to decide if then() is called, because if handleCatch() rethrows, then then() will not be called, since the exception will be thrown to the caller immediately. If handleCatch() can gracefully handle the issue, then then() will be called.
Related
I'm pretty new to JavaScript (using Node.js) and still learning. I try to wrap around my head with promises and I got into this situation where I don't understand if there is a difference between this code:
promiseFunc().then(() => {
anotherPromiseFunc() // I dont need .then() here, just want to save some data to DB
});
doSmthElse()
and this code:
promiseFunc().then(async () => {
await anotherPromiseFunc() // I dont need .then() here, just want to save some data to DB
});
doSmthElse()
If I don't use .then() in the first case, does it mean there is a possibility that doSmthElse() will be executed before anotherPromiseFunc() is executed? So in order to prevent that, I have to add async/await? Or all code in .then() block is being waited to execute anyway before doing something else?
Note 1: I don't want to chain those promises, because there is more code in my case, but I just simplified it here.
Note 2: I don't use catch because if error will rip through I will catch it later.
If I don't use .then() in the first case, does it mean there is a possibility that doSmthElse() will be executed before AnotherPromise() is executed?
doSmthElse() is guaranteed to be executed before anything in the fulfillment handler¹ is executed. Promise fulfillment and rejection handlers are always invoked asynchronously. That's true whether you declare the handler function using async or not.
For example:
console.log("before");
Promise.resolve(42)
.then(result => {
console.log("within", result);
});
console.log("after");
The output of that is:
before
after
within 42
So in order to prevent that, I have to add async/await?
Where you've added async/await in your example doesn't change when doSmthElse() is executed.
If you want doSmthElse() to wait until the promise has been fulfilled, move it into the fulfillment handler.¹
If your code were inside an async function, you could use await instead, like this:
// Inside an `async` function
await promiseFunc().then(() => {
anotherPromiseFunc();
});
doSmthElse()
That would do this:
Call promiseFunc() and get the promise it returns
Hook up a fulfillment handler to that promise via then, returning a new promise; that new promise is the operand to await
Wait for the promise from #1 to settle
When it does, your fulfillment handler is executed and runs anothterPromiseFunc() but doesn't wait for the promise it returns to settle (because, as you said, you're not returning that promise from the fulfillment handler).
At this point, the promise from #2 is fulfilled because your fulfillment handler has (effectively) returned undefined, which isn't a thenable (loosely, a promise), so that value (undefined) is used to fulfill the promise from #2.
Since the promise from #2 has been fulfilled, await is satisfied and doSmthElse() is executed.
¹ the function you pass then as its first argument
I assume that Promise() just stands for some function call returning a Promise:
You could say that .then registers an event listener that runs as soon as the promise settles => the task is finished. It still runs asynchronously So in your example doSmthElse will still run even if the promise hasn't been settled (so if the promise doesn't settle immediately doSmthElse will be called before the function inside .then)
To let your code run "in order". You would have to use await to ensure that doSmthElse is called after the promise settled or you could put doSmthElse into the .then block.
function fetchDog(){
fetch("https://dog.ceo/api/breeds/image/fail")
.then(response => response.json())
.then(data => console.log(data))
.catch(function(err) {
console.log('Fetch problem');
});
};
fetchDog();
Using the above example I would like to clarify how .catch here receives the rejected Promise. It's also a good exercise in reading MDN for me.
1 .
Looking at this statement from MDN:
If the Promise that then is called on adopts a state (fulfillment or
rejection) for which then has no handler, a new Promise is created
with no additional handlers, simply adopting the final state of the
original Promise on which then was called.
I translate that to mean, in my example, the .thens return a new promise that is a copy of the promise that .then was called on.
2 .
The Promise.prototype.catch spec also says it behaves the same as calling Promise.prototype.then(undefined, onRejected).
I interpret this to mean, in my example, that the first callback in catch is the onRejected parameter. Therefore, when catch receives a rejected promise, it executes console.log('Fetch problem');.
I also interpret this to mean that when catch invariably receives a fulfilled promise, it returns undefined? (I haven't thought of a way to test this in the console).
3 .
I also read in the .then spec:
If a handler function: doesn't return anything, the promise returned
by then gets resolved with an undefined value.
Therefore, in my code snippet, I interpret this to mean catch returns a promise whose value is undefined.
Based on this understanding, so long as the fetch line returned a fulfilled promise, this fulfilled promise would find its way to catch and catch's callback wouldn't execute. catch would return undefined. (I can't think of a way to test this). I suspect my understanding is wrong.
When catch invariably receives a fulfilled promise, it returns undefined?
No. Calling .catch() will always return a promise. It does that before even knowing whether the promise that it was called on is fulfilled, rejected or still pending.
I interpret this to mean catch returns a promise whose value is undefined.
Yes. You can test this easily with
function handleError(p1) {
const p2 = p1.catch(err => {
console.log('handling problem', err);
});
p2.then(res => {
console.log('final promise fulfilled with', res);
});
}
// handleError(Promise.resolve('success'));
handleError(Promise.reject('error'));
I'm wanting to clarify how a promise is passed to .catch and what .catch does with it.
Using this as an example:
function fetchDog(){
fetch("https://dog.ceo/api/breeds/image/fail")
.then(response => response.json())
.then(data => console.log(data))
.catch(function(err) {
console.log('Fetch problem');
});
};
fetchDog();
Looking at this statement from MDN:
If the Promise that then is called on adopts a state (fulfillment or
rejection) for which then has no handler, a new Promise is created
with no additional handlers, simply adopting the final state of the
original Promise on which then was called.
I translate that to mean, in my example, the .thens return a new promise that is a copy of the promise that .then was called on.
By the time it reaches .catch, I know that .catch prints something to the console. The spec also says it behaves the same as calling Promise.prototype.then(undefined, onRejected).
Therefore, based on this excerpt from the .then spec:
If a handler function: doesn't return anything, the promise returned
by then gets resolved with an undefined value.
I expect .catch to return a new promise that 'gets resolved with' an undefined value. (What exactly does that mean, for a promise object to get 'resolved with' an undefined value)?
Is this true?
.then() calls its callback when the promise is resolved. .catch() only calls its callback when the promise is rejected.
If fetch() is successful, it resolves its promise, so only the .then() callbacks are called.
If fetch() gets an error, it rejects its promise, and the .catch() callbacks will be called. Also, if response.json() gets an error (e.g. the response was not valid JSON), it will reject the promise, and .catch() will call its callback.
So I am wondering if this works?
S3.getObject()
.promise()
.then()
.catch() // catch error from the first then() statement
.then()
.catch() // catch error from the second then() statement
or do I need to place all 'catches' in the end? Can I have multiple catch then? Will they be fired in the order of the 'then' statements throwing errors?
It depends of your actual goals.
As a matter of fact, .then() method takes two parameters:
onFullfilled: Callback to be invoked when the promise is fulfilled.
onRejected: Callback to be invoked when the promise is rejected.
In fact, .catch(fn) is just a shorthand for .then(null, fn).
Both .then() and .catch() each return a new promise which resolves to its return value. In other words:
A resolved promise of that value if it isn't a promise.
The actual return value if it is already a promise (that will be fulfilled or rejected).
A rejected promise if the return value is a rejected promise (as previous point says) or any error is thrown.
The main reason behind the use of .then(onFullfill).catch(onReject) pattern instead of .then(onFullfill, onReject) is that, in the former (which is equivalent to .then(onFullfill).then(null, onReject)), we are chaining the onReject callback to the promise returned by first .then() instead of directly to the original promise.
The consequence of this is that if en error is thrown inside the onFullfill callback (or it returns a promise which happen to resolve to a rejected state), it will be catched by the chained .catch() too.
So, answering to your question, when you do something like:
P.then(...)
.then(...)
.then(...)
.catch(...)
;
You are chaining promises "supposing" all will go fine "and only check at the end". That is: Whenever any step fails, all subsequent .then()s are bypassed up to the next (in this case the last) .catch().
On the other hand, if you insert more .catch()s in between, you would be able to intercept rejected promises earlier and, if appropriate, solve whatever were going on and turn it into a resolved state again in order to resume the chain.
I have trouble understanding the difference between putting .catch BEFORE and AFTER then in a nested promise.
Alternative 1:
test1Async(10).then((res) => {
return test2Async(22)
.then((res) => {
return test3Async(100);
}).catch((err) => {
throw "ERROR AFTER THEN";
});
}).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
});
Alternative 2:
test1Async(10).then((res) => {
return test2Async(22)
.catch((err) => {
throw "ERROR BEFORE THEN";
})
.then((res) => {
return test3Async(100);
});
}).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
});
The behavior of each function is as follow, test1 fail if number is <0 test2 fails if number is > 10 and test3 fails if number is not 100. In this case test2 is only failing.
I tried to run and make test2Async fail, both BEFORE and AFTER then behaves the same way and that is not executing the test3Async. Can somebody explain to me the main difference for placing catch in different places?
In each function I console.log('Running test X') in order to check if it gets executed.
This question arises because of the previous thread I posted How to turn nested callback into promise?. I figure it is a different problem and worth posting another topic.
So, basically you're asking what is the difference between these two (where p is a promise created from some previous code):
return p.then(...).catch(...);
and
return p.catch(...).then(...);
There are differences either when p resolves or rejects, but whether those differences matter or not depends upon what the code inside the .then() or .catch() handlers does.
What happens when p resolves:
In the first scheme, when p resolves, the .then() handler is called. If that .then() handler either returns a value or another promise that eventually resolves, then the .catch() handler is skipped. But, if the .then() handler either throws or returns a promise that eventually rejects, then the .catch() handler will execute for both a reject in the original promise p, but also an error that occurs in the .then() handler.
In the second scheme, when p resolves, the .then() handler is called. If that .then() handler either throws or returns a promise that eventually rejects, then the .catch() handler cannot catch that because it is before it in the chain.
So, that's difference #1. If the .catch() handler is AFTER, then it can also catch errors inside the .then() handler.
What happens when p rejects:
Now, in the first scheme, if the promise p rejects, then the .then() handler is skipped and the .catch() handler will be called as you would expect. What you do in the .catch() handler determines what is returned as the final result. If you just return a value from the .catch() handler or return a promise that eventually resolves, then the promise chain switches to the resolved state because you "handled" the error and returned normally. If you throw or return a rejected promise in the .catch() handler, then the returned promise stays rejected.
In the second scheme, if the promise p rejects, then the .catch() handler is called. If you return a normal value or a promise that eventually resolves from the .catch() handler (thus "handling" the error), then the promise chain switches to the resolved state and the .then() handler after the .catch() will be called.
So that's difference #2. If the .catch() handler is BEFORE, then it can handle the error and allow the .then() handler to still get called.
When to use which:
Use the first scheme if you want just one .catch() handler that can catch errors in either the original promise p or in the .then() handler and a reject from p should skip the .then() handler.
Use the second scheme if you want to be able to catch errors in the original promise p and maybe (depending upon conditions), allow the promise chain to continue as resolved, thus executing the .then() handler.
The other option
There's one other option to use both callbacks that you can pass to .then() as in:
p.then(fn1, fn2)
This guarantees that only one of fn1 or fn2 will ever be called. If p resolves, then fn1 will be called. If p rejects, then fn2 will be called. No change of outcome in fn1 can ever make fn2 get called or vice versa. So, if you want to make absolutely sure that only one of your two handlers is called regardless of what happens in the handlers themselves then you can use p.then(fn1, fn2).
jfriend00's answer is excellent, but I thought it would be a good idea to add the analogous synchronous code.
return p.then(...).catch(...);
is similar to the synchronous:
try {
iMightThrow() // like `p`
then()
} catch (err) {
handleCatch()
}
If iMightThrow() doesn't throw, then() will be called. If it does throw (or if then() itself throws), then handleCatch() will be called. Notice how the catch block has no control over whether or not then is called.
On the other hand,
return p.catch(...).then(...);
is similar to the synchronous:
try {
iMightThrow()
} catch (err) {
handleCatch()
}
then()
In this case, if iMightThrow() doesn't throw, then then() will execute. If it does throw, then it would be up to handleCatch() to decide if then() is called, because if handleCatch() rethrows, then then() will not be called, since the exception will be thrown to the caller immediately. If handleCatch() can gracefully handle the issue, then then() will be called.