Reputation: 1
In the company I work in, everything is currently done with callbacks. We are starting to write small components with promises that the big code depends of. We started having trouble with them.
function getSomething() {
return Promise.resolve('hello')
}
function test(cb) {
getSomething()
.then(string => {
a.s
cb(null, string)
}, error => cb(error))
}
test((error, result) => {
console.log(error)
console.log(result)
a.s
})
This is a simple example of the problem. In this code since a does not exist it would throw a warning UnhandledPromiseRejectionWarning and kill the process. console logs are never reached.
The logic behind was that if an error ever happens it would trigger the catch callback.
function test(cb) {
getSomething()
.then(string => {
// a.s
cb(null, string)
}, error => cb(error))
.catch(error => cb(error))
}
I was advised to use the explicit catch at the end of the promise chain. The problem is if in the callback an error is thrown, the callback would trigger twice.
Thanks for any help.
Upvotes: 0
Views: 702
Reputation: 320
What happens in your code snippet is, that you return a rejected promise when an error occurs within your then()
.
And as you don't have any handler after that, this rejected promise is not handled and (fortunately) pops up.
You mainly have two possibilities to fix this:
You could either add another catch()
after your then()
method like this
function test(cb) {
getSomething()
.then(string => {
a.s
cb(null, string)
})
.catch(e => cb) //this catch() handles whatever rejected promise
//comes out of then() or from the earlier promise
}
In this solution it may happen, that you get your callback called twice in case an error is thrown while executing cb(null, string). In that case you need a guard in your callback to distinguish different error codes to find out whether it comes from getSomething() or from cb(null, string).
Or you could add a traditional try/catch-block within your then()
handler like this
function test(cb) {
getSomething()
.then(string => {
try{
a.s
cb(null, string)
} catch(e) {
if(e.message == "callback error" {
//do what you want to do to with a callback error
} else {
//do whatever you want to do with other errors from your catch block
}
}
}, error => cb(error))
}
In this solution you can put the guard to distinguish different error reasons inside the catch block and have the callback called only once. However you need another handling mechanism to handle your errors. One easy possibility would be to silently swallow the new errors, however this might be a bad solution.
That's the problem with asynchronous programming, that there is no possibility for errors to bubble up to a central handler as the stack context is lost till the callback is finally executed.
Cheers, Felix
Upvotes: 0
Reputation: 350079
You could separate the code in the then
part so that errors before calling cb are treated differently than the ones that occur within cb. Instead of calling cb there, you would return the string value so that it can be used in a chained then
method. And there you can use the switch between success and failure. That way cb will only be called there.
Now if by calling cb an error occurs, you could catch that with a final .catch
, but then you would not call cb anymore, but maybe just output something, or cascade the error, or do whatever you want to do in that case.
Here is a demo with three use cases:
then
function getSomething() {
return Promise.resolve('hello');
}
function test(testNo, cb) {
getSomething()
.then(string => {
if (testNo == 2) a.s;
return string; // don't call cb yet
})
.then(cb.bind(null, null), cb) // now call it -- mutually exclusive with error case
.catch(error => { // catch any errors not yet captured -- i.e. in cb
console.log('test ' + testNo + ': error occurred in success callback');
});
}
for (let testNo = 1; testNo <= 3; testNo++) {
test(testNo, (error, result) => {
if (error)
console.log('callback for test ' + testNo + ' error:', error.message);
else
console.log('callback for test ' + testNo + ' success:', result);
if (testNo == 3) a.s;
});
}
Upvotes: 0
Reputation: 26161
There is a subtle difference between
.then(onFulfilled, onRejected)
and
.then(onFullfilled)
.catch(onRejected)
The first one wouldn't be able to catch the errors thrown within the onFullFilled
callback while the second one would. So may be you should not use the onRejected
callback at the then stage and just chain a .catch()
like you did in your second snippet.
Upvotes: 1