-
Notifications
You must be signed in to change notification settings - Fork 5
Error Handling
When used for synchronous iterables, you can resort to the regular try/catch
at the point where iteration
is triggered in your code. This however presents some problems:
- Reusable iterables can trigger different errors, depending on usage + input data, which means manual error handling will be required at every point of triggering an iteration.
- Verbose error-handling coding around every use of iterables is a fairly poor and time-consuming practice.
- It doesn't really work for asynchronous iterables, which this library fully supports
That's why here we are covering error handling strictly during/inside iteration, as values are retrieved one by one. This library supports explicit + implicit ways of handling iteration errors, as documented below.
You can handle all upstream iteration errors with a catchError operator at the end of the pipeline:
pipe(iterable, ...operators, catchError((error, ctx) => {
// Handling the error (logging it, etc)
// After that, you can do any of the following:
//
// - nothing (we let it skip the value);
// - provide a new/alternative value (via ctx.emit(value));
// - re-throw the original error;
// - throw a new error.
}))
Parameters passed into the error handler:
-
error
- the original error that was thrown; -
ctx
- iteration error context - see IErrorContext type.
When the handler wants to provide an alternative iteration value, it has to call ctx.emit(value)
.
You can use catch
on any piped iterable:
pipe(iterable, ...operators)
.catch((error, ctx) => {
});
This will simply append catchError to the end of the pipeline, so the result is the same as with the explicit error handling. However, this adds the flexibility of handling errors separately from the pipeline implementation.
You can chain as many error handlers as you want, each handling what's upstream, after the last error handler.
For explicit error handling, this means you can have any number of catchError operators inside the pipeline:
pipe(iterable, operator1, catchError, operator2, operator3, catchError);
//=> first catchError will handle errors thrown by operator1
//=> second catchError will handle errors thrown by operator2 and operator3
//=> second catchError will handle all errors, if the first catchError re-throws
For implicit error handling, this means you can chain any number of catch
handlers at the end of the pipeline,
each will be appending another catchError to the end of the pipeline:
pipe(iterable, ...operators)
.catch() // first handler
.catch() // second handler
.catch() // third handler
// this will produce:
// pipe(iterable, ...operators, catchError, catchError, catchError);
Error handling here is for the iteration itself. When an iteration error occurs, it usually means method next
in one of the iterators threw an error, and as such, it will continue doing so repeatedly. So, if you just handle the error, but without re-throwing it, you are likely to end up with the same error being reported in an endless loop.
There are two ways to avoid it...
First, you can simply re-throw the error:
.catch(err => {
console.log(err); // log the error somewhere
throw e; // re-throw the error, to exit the iteration
})
A smarter way is to re-throw after detecting a repeated error. This is why we have repeats
property in the error context object:
.catch((err, ctx) => {
if(ctx.repeats) {
throw e; // re-throw when repeated
}
console.log(err); // log the error somewhere
})