With promises, we can use a variant of .then to split up the chain when an error occurs. Here is an example using fetch
fetch('http://website.com').then(
// Perform some logic
(response) => response.json().then(({ answer }) => `Your answer: ${answer}`),
// Skip json parsing when an error occurs
(error) => 'An error occurred :(',
).then(console.log);
This allows me to skip the response processing logic and only respond to errors that were raised in the original fetch statement. Something similar in RxJS might look like this:
Observable.fromPromise(fetch('http://website.com'))
// if I put .catch here, the result will be piped into flatMap and map
.flatMap(response => response.json())
.map(({ answer }) => `Your answer: ${answer}`)
// if I put .catch here, errors thrown in flatMap and map will also be caught
.subscribe(console.log);
As the comments in the code state, I can't simply put a catch operator in as it doesn't have the same behaviour as my promise chain.
I know I can get it with custom operators involving materialising, or merging an error catching observable with this one, but it all seems like pretty major overkill. Is there a simple way to achieve the promise chain behaviour?
Actually, if I was in your situation I wouldn't be worried about catching errors from flatMap and map. When the source Observable throws an error then it'll be propagated to the observer. So I'd just use an error handler when calling subscribe (otherwise the error is rethrown):
.subscribe(console.log, err => console.log('error:', err));
Note that when an error occurs in the source Observable (a Promise in your case) that it's propagated as error notifications, not as standard next notifications. This means that flatMap() and map() won't have absolutely any effect on the error message. If you used catch() or materialize() that both operators (flatMap and map) would have to be able to handle this type of data (and not throw yet another error).
Anyway you can always use share() or publish() and make two different subscriptions where each handles only one type of signals:
let source = Observable.fromPromise(fetch('http://website.com')).publish();
source
.subscribe(undefined, err => console.log(err));
source
.flatMap(...)
.map(...)
.subscribe(console.log, () => {});
source.connect();
Now I have a separate observer only for errors.
Note that I had to make an empty callback with () => {} so the error will be silently ignored. Also note that when using multicasting (the publish() operator) then the Subject inside might have some specific behavior I should be aware of but maybe it doesn't matter in your use-case.
Related
I have a function which gets given a list of ids and then maps over that list and calls an API for each one:
let fullDetails: Array<any> = [];
ids.map(async (id) => {
try {
const result = await api.getFullDetails(id);
if (result.data) fullDetails.push(result.data);
} catch {
// Handle error
}
});
The problem I'm having is that sometimes the getFullDetails function will return an error just because the record it's looking for doesn't exist. But, I don't really care if the record doesn't exist (to be honest, I don't really care about any errors here) - I'm happy to just skip that one and move on to the next. No matter what I do, though, my code seems to bail out at that point if the result is an error.
I've tried leaving out the try-catch block, but then I get a 'Possible unhandled Promise rejection' error and fullDetails remains empty (although I know for sure that one of the ids worked ok).
I also tried rewriting to use Promise.all, like this:
let results = ids.map((id) =>
api.getFullDetails(id),
);
Promise.all(results)
.then((result) => {
console.log(result);
})
.catch((error) => { console.log('Error')});
but again, it goes into the catch block if there's any kind of error. Again I tried leaving out the catch block here, but then I got the 'Possible unhandled Promise rejection' error again, and the result was never shown.
Is there a way to handle this (apart form rewriting the API to not return an error)? Basically I just don't want to check for errors at all, and just ignore them if they occur.
The proper way to use map with async is to use one of the Promise's methods.
It's better to choose Promise.allSettled() rather than Promise.all() in your case. Because, according to MDN,
In comparison, the Promise returned by Promise.all() may be more appropriate if the tasks are dependent on each other, or if you'd like to immediately reject upon any of them rejecting.
Meaning the former case won't reject promises and stop the program execution.
Note that the allSettled() returns an object with two of this three properties:
[{
reason: "Id must be non-negative",
status: "rejected"
}, {
status: "fulfilled",
value: 2
}]
The first case happens when the Promise is rejected and the second when it's fullfiled.
Promise.allSettled(ids.map((id) => api.getFullDetails(id))).then(x => {
let fullDetails = x.map(({value})=>value).filter(Boolean)
console.log(fullDetails)
})
As seen in this working example.
I was wondering whether there's a way to run a function in JavaScript and let the program ignore it if there's an error running the function?
player.play('./sounds/throughQueue.mp3', function(err){
if (err) throw err
})
Pretty much like this without the "throw err".
The program should just continue.
Looking forward to your answers.
As you mentioned in comments you want to know how to handle async function error;
You could do something like this:
function myAsyncFunction(){
return new Promise((resolve, reject) => {
// do some async anction that returns error
foo.asyncCall((err, item) => err && reject(err))
}
}
myAsyncFunction().catch(e => console.error(e))
If you observe code above async error was handled in two styles:
Callback function usually has 1st arg returning error and you can do if (err) and add any logic you want in there. Or just ignore it.
Within Promise you can use reject which is second argument in constructor. And when you reject error, you can use .catch method upon that Promise and use any custom logic you want, either new promise call or function callback.
Note I used console.error within callback function, you could just do nothing there and it will be ignored (your program will continue)
Yes, you can ignore callback/promise error or swallow try/catch caught error, but it can only make the execution go on, not make the behavior or result correctness.
If in the callback function, you can ignore error with empty error handling and the execution won't break either, but the behavior is still dependent on if there is error in the invocation.
If in promise then, you can also ignore errors caught.
If in async/await way, you must try/catch the invocation and swallow the caught err.
In a word, I suggest you handle the possible errors. after all, quality software is what we want. and what maybe happen will happen eventually.
I am playing around with Rxjs, observables and maps, and I found a strange behavior for Observable.throw(error) that I cannot explain.
If I have a Rx stream that uses a map operator, and I want to interrupt the process, I would have expected the method Observable.throw to be appropriate, however, this does not seem to be the case.
Consider the following example:
Rx.Observable.just("test 1").subscribe(
x => console.log(x),
err => console.log(err),
() => console.log("done, no errors")
);
Now let's introduce an error, if I use the regular throw from Javascript it works as expected:
Rx.Observable.just("test 2").map(
x => { throw new Error(x) }
).subscribe(
x => console.log(x),
err => console.log(err),
() => console.log("done - error was thrown, as expected")
);
Output:
Error: test 2
at Rx.Observable.just.map.x ((index):58)
at c (rx.all.compat.js:61)
at e.onNext (rx.all.compat.js:5169)
(...)
But if I use Rx.Observable.throw(...), the error callback from the following subscriber is never called, and the next callback will be called instead with some strange object that seems to be an Rx error object.
Rx.Observable.just("test 3").map(
x => Rx.Observable.throw(x)
).subscribe(
x => console.log(x),
err => console.log(err),
() => console.log("done - it does not work... why?")
);
Output:
b_subscribe: f(a)error: "test 3" scheduler: a__proto__: g
As #Whymarrh pointed out, it seems to work fine if I use a flatMap operator instead.
Documentation for map:
The Map operator applies a function of your choosing to each item
emitted by the source Observable, and returns an Observable that emits
the results of these function applications.
Documentation for Observable.throw:
Returns an observable sequence that terminates with an exception,
using the specified scheduler to send out the single onError message.
Does anyone know why when using Observable.throw inside the map operator the error callback is not called, and why the process is not interrupted?
Example in jsfiddle
I know that I can just use the regular throw and move on, I already have a working solution, I am posting this question out of curiosity to have a better understanding of how the framework works.
Quick reminder: stackoverflow has a be nice policy.
One of the comments properly answers this question:
throw new Error(x) throws an exception that simply bubbles to the top of the process.
x => Rx.Observable.throw(x) just creates a structure representing an error. To the map operator, this structure is a value like any other, to call the success handler. flatMap on the other hand will take the structure and unwrapped it, then call the error handler.
I had a look at the bluebird promise FAQ, in which it mentions that .then(success, fail) is an antipattern. I don't quite understand its explanation as for the try and catch.
What's wrong with the following?
some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })
It seems that the example is suggesting the following to be the correct way.
some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })
What's the difference?
What's the difference?
The .then() call will return a promise that will be rejected in case the callback throws an error. This means, when your success logger fails, the error would be passed to the following .catch() callback, but not to the fail callback that goes alongside success.
Here's a control flow diagram:
To express it in synchronous code:
// some_promise_call().then(logger.log, logger.log)
then: {
try {
var results = some_call();
} catch(e) {
logger.log(e);
break then;
} // else
logger.log(results);
}
The second log (which is like the first argument to .then()) will only be executed in the case that no exception happened. The labelled block and the break statement feel a bit odd, this is actually what python has try-except-else for (recommended reading!).
// some_promise_call().then(logger.log).catch(logger.log)
try {
var results = some_call();
logger.log(results);
} catch(e) {
logger.log(e);
}
The catch logger will also handle exceptions from the success logger call.
So much for the difference.
I don't quite understand its explanation as for the try and catch
The argument is that usually, you want to catch errors in every step of the processing and that you shouldn't use it in chains. The expectation is that you only have one final handler which handles all errors - while, when you use the "antipattern", errors in some of the then-callbacks are not handled.
However, this pattern is actually very useful: When you want to handle errors that happened in exactly this step, and you want to do something entirely different when no error happened - i.e. when the error is unrecoverable. Be aware that this is branching your control flow. Of course, this is sometimes desired.
What's wrong with the following?
some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })
That you had to repeat your callback. You rather want
some_promise_call()
.catch(function(e) {
return e; // it's OK, we'll just log it
})
.done(function(res) {
logger.log(res);
});
You also might consider using .finally() for this.
The two aren't quite identical. The difference is that the first example won't catch an exception that's thrown in your success handler. So if your method should only ever return resolved promises, as is often the case, you need a trailing catch handler (or yet another then with an empty success parameter). Sure, it may be that your then handler doesn't do anything that might potentially fail, in which case using one 2-parameter then could be fine.
But I believe the point of the text you linked to is that then is mostly useful versus callbacks in its ability to chain a bunch of asynchronous steps, and when you actually do this, the 2-parameter form of then subtly doesn't behave quite as expected, for the above reason. It's particularly counterintuitive when used mid-chain.
As someone who's done a lot of complex async stuff and bumped into corners like this more than I care to admit, I really recommend avoiding this anti-pattern and going with the separate handler approach.
By looking at advantages and disadvantages of both we can make a calculated guess as to which is appropriate for the situation.
These are the two main approaches to implementing promises. Both have it's pluses and minus
Catch Approach
some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })
Advantages
All errors are handled by one catch block.
Even catches any exception in the then block.
Chaining of multiple success callbacks
Disadvantages
In case of chaining it becomes difficult to show different error messages.
Success/Error Approach
some_promise_call()
.then(function success(res) { logger.log(res) },
function error(err) { logger.log(err) })
Advantages
You get fine grained error control.
You can have common error handling function for various categories of errors like db error, 500 error etc.
Disavantages
You will still need another catch if you wish to handler errors thrown by the success callback
Simple explain:
In ES2018
When the catch method is called with argument onRejected, the
following steps are taken:
Let promise be the this value.
Return ? Invoke(promise, "then", « undefined, onRejected »).
that means:
promise.then(f1).catch(f2)
equals
promise.then(f1).then(undefiend, f2)
Using .then().catch() lets you enable Promise Chaining which is required to fulfil a workflow. You may need to read some information from database then you want to pass it to an async API then you want to manipulate the response. You may want to push the response back into the database. Handling all these workflows with your concept is doable but very hard to manage. The better solution will be then().then().then().then().catch() which receives all errors in just once catch and lets you keep the maintainability of the code.
Using then() and catch() helps chain success and failure handler on the promise.catch() works on promise returned by then(). It handles,
If promise was rejected. See #3 in the picture
If error occurred in success handler of then(), between line numbers 4 to 7 below. See #2.a in the picture
(Failure callback on then() does not handle this.)
If error occurred in failure handler of then(), line number 8 below. See #3.b in the picture.
1. let promiseRef: Promise = this. aTimetakingTask (false);
2. promiseRef
3. .then(
4. (result) => {
5. /* successfully, resolved promise.
6. Work on data here */
7. },
8. (error) => console.log(error)
9. )
10. .catch( (e) => {
11. /* successfully, resolved promise.
12. Work on data here */
13. });
Note: Many times, failure handler might not be defined if catch() is
written already.
EDIT: reject() result in invoking catch() only if the error
handler in then() is not defined. Notice #3 in the picture to
the catch(). It is invoked when handler in line# 8 and 9 are not
defined.
It makes sense because promise returned by then() does not have an error if a callback is taking care of it.
Instead of words, good example. Following code (if first promise resolved):
Promise.resolve()
.then
(
() => { throw new Error('Error occurs'); },
err => console.log('This error is caught:', err)
);
is identical to:
Promise.resolve()
.catch
(
err => console.log('This error is caught:', err)
)
.then
(
() => { throw new Error('Error occurs'); }
)
But with rejected first promise, this is not identical:
Promise.reject()
.then
(
() => { throw new Error('Error occurs'); },
err => console.log('This error is caught:', err)
);
Promise.reject()
.catch
(
err => console.log('This error is caught:', err)
)
.then
(
() => { throw new Error('Error occurs'); }
)
I've done a lot of async coding in Node.js with callbacks and the excellent async library which works great. I'm trying to use a module that uses promises but I'm running into a problem where any errors thrown AFTER the promise are still bubbled up and caught by the promise error handler.
This makes it very difficult to debug errors as I have no idea where they will pop up and they can't be thrown and don't crash the app.
Example code below; all I want to do is to exit the promise chain and leave it behind once it has been resolved, rather than it catching all subsequent errors that aren't anything to do with it.
function one (input, callback) {
doSomeAsyncWork(input)
.then(function (result) {
return callback(null, result);
})
.catch(function (err) {
logError(err);
return callback(err);
});
}
function two (err, result) {
if (err) { ... }
var x = callAMethodThatThrows();
...
}
one('abc', two);
In this example, the method callAMethodThatThrows() throws an error, which gets bubbled up to the promise catch() block. This prevents the app from crashing and leaves it in an unknown state.
Any advice would be really appreciated, thanks.
Yes, sorry about that - we're getting to fixing(1) the default behavior in Node. In the meanwhile I specced and Petka added (with support from others) a hook for finding these errors:
process.on("unhandledRejection", (err, p) => {
console.error(err); // print the error
});
Note that this might catch some false negatives if you're performing .catch asynchronously itself - in my experience that's very rare.
Also note that with promises your server typically isn't in an unknown state and you can and should attempt to recover from errors when it makes sense. Since promises all the way means throw-safe code you can have fine grained error handling.
Note that promises make the async library largely unneeded. If you still wish to use callbacks and just wish "those pesky promises" would leave you alone and let you keep writing callbacks, that's fine - promises are throw safe but you can escape that by executing things off the promise code:
myPromiseFn().then(v => {
process.nextTick(() => cb(null, v)); // next tick, to escape the chain
}, e => process.nextTick(() => cb(e));
Note that fair promise libraries also come with a asCallback callback for converting the promise code to a node err-back callback.
(1) Some people claim there is no problem, go figure
Thanks to Ben's answer I discovered it's possible to convert a promise to a callback using a module such as nodeify. This allows us to keep all our code using callbacks, and avoids errors getting swallowed by the promise. Very useful.