diff --git a/src/operator/catch.ts b/src/operator/catch.ts index 1a2f470783..e39fc1e2b2 100644 --- a/src/operator/catch.ts +++ b/src/operator/catch.ts @@ -1,9 +1,6 @@ -import { Operator } from '../Operator'; -import { Subscriber } from '../Subscriber'; -import { Observable, ObservableInput } from '../Observable'; -import { OuterSubscriber } from '../OuterSubscriber'; -import { subscribeToResult } from '../util/subscribeToResult'; +import { Observable, ObservableInput } from '../Observable'; +import { catchError as higherOrder } from '../operators'; /** * Catches errors on the observable to be handled by returning a new observable or throwing an error. @@ -65,50 +62,5 @@ import { subscribeToResult } from '../util/subscribeToResult'; * @owner Observable */ export function _catch(this: Observable, selector: (err: any, caught: Observable) => ObservableInput): Observable { - const operator = new CatchOperator(selector); - const caught = this.lift(operator); - return (operator.caught = caught); -} - -class CatchOperator implements Operator { - caught: Observable; - - constructor(private selector: (err: any, caught: Observable) => ObservableInput) { - } - - call(subscriber: Subscriber, source: any): any { - return source.subscribe(new CatchSubscriber(subscriber, this.selector, this.caught)); - } -} - -/** - * We need this JSDoc comment for affecting ESDoc. - * @ignore - * @extends {Ignored} - */ -class CatchSubscriber extends OuterSubscriber { - constructor(destination: Subscriber, - private selector: (err: any, caught: Observable) => ObservableInput, - private caught: Observable) { - super(destination); - } - - // NOTE: overriding `error` instead of `_error` because we don't want - // to have this flag this subscriber as `isStopped`. We can mimic the - // behavior of the RetrySubscriber (from the `retry` operator), where - // we unsubscribe from our source chain, reset our Subscriber flags, - // then subscribe to the selector result. - error(err: any) { - if (!this.isStopped) { - let result: any; - try { - result = this.selector(err, this.caught); - } catch (err2) { - super.error(err2); - return; - } - this._unsubscribeAndRecycle(); - this.add(subscribeToResult(this, result)); - } - } + return higherOrder(selector)(this); } diff --git a/src/operators/catchError.ts b/src/operators/catchError.ts new file mode 100644 index 0000000000..ea9eec5610 --- /dev/null +++ b/src/operators/catchError.ts @@ -0,0 +1,115 @@ +import { Operator } from '../Operator'; +import { Subscriber } from '../Subscriber'; +import { Observable, ObservableInput } from '../Observable'; + +import { OuterSubscriber } from '../OuterSubscriber'; +import { subscribeToResult } from '../util/subscribeToResult'; +import { OperatorFunction } from '../interfaces'; + +/** + * Catches errors on the observable to be handled by returning a new observable or throwing an error. + * + * + * + * @example Continues with a different Observable when there's an error + * + * Observable.of(1, 2, 3, 4, 5) + * .map(n => { + * if (n == 4) { + * throw 'four!'; + * } + * return n; + * }) + * .catch(err => Observable.of('I', 'II', 'III', 'IV', 'V')) + * .subscribe(x => console.log(x)); + * // 1, 2, 3, I, II, III, IV, V + * + * @example Retries the caught source Observable again in case of error, similar to retry() operator + * + * Observable.of(1, 2, 3, 4, 5) + * .map(n => { + * if (n === 4) { + * throw 'four!'; + * } + * return n; + * }) + * .catch((err, caught) => caught) + * .take(30) + * .subscribe(x => console.log(x)); + * // 1, 2, 3, 1, 2, 3, ... + * + * @example Throws a new error when the source Observable throws an error + * + * Observable.of(1, 2, 3, 4, 5) + * .map(n => { + * if (n == 4) { + * throw 'four!'; + * } + * return n; + * }) + * .catch(err => { + * throw 'error in source. Details: ' + err; + * }) + * .subscribe( + * x => console.log(x), + * err => console.log(err) + * ); + * // 1, 2, 3, error in source. Details: four! + * + * @param {function} selector a function that takes as arguments `err`, which is the error, and `caught`, which + * is the source observable, in case you'd like to "retry" that observable by returning it again. Whatever observable + * is returned by the `selector` will be used to continue the observable chain. + * @return {Observable} An observable that originates from either the source or the observable returned by the + * catch `selector` function. + * @name catchError + */ +export function catchError(selector: (err: any, caught: Observable) => ObservableInput): OperatorFunction { + return function catchErrorOperatorFunction(source: Observable): Observable { + const operator = new CatchOperator(selector); + const caught = source.lift(operator); + return (operator.caught = caught); + }; +} + +class CatchOperator implements Operator { + caught: Observable; + + constructor(private selector: (err: any, caught: Observable) => ObservableInput) { + } + + call(subscriber: Subscriber, source: any): any { + return source.subscribe(new CatchSubscriber(subscriber, this.selector, this.caught)); + } +} + +/** + * We need this JSDoc comment for affecting ESDoc. + * @ignore + * @extends {Ignored} + */ +class CatchSubscriber extends OuterSubscriber { + constructor(destination: Subscriber, + private selector: (err: any, caught: Observable) => ObservableInput, + private caught: Observable) { + super(destination); + } + + // NOTE: overriding `error` instead of `_error` because we don't want + // to have this flag this subscriber as `isStopped`. We can mimic the + // behavior of the RetrySubscriber (from the `retry` operator), where + // we unsubscribe from our source chain, reset our Subscriber flags, + // then subscribe to the selector result. + error(err: any) { + if (!this.isStopped) { + let result: any; + try { + result = this.selector(err, this.caught); + } catch (err2) { + super.error(err2); + return; + } + this._unsubscribeAndRecycle(); + this.add(subscribeToResult(this, result)); + } + } +} diff --git a/src/operators/index.ts b/src/operators/index.ts index a4838cc8d9..43b6ee858e 100644 --- a/src/operators/index.ts +++ b/src/operators/index.ts @@ -1,3 +1,4 @@ +export { catchError } from './catchError'; export { concatMap } from './concatMap'; export { defaultIfEmpty } from './defaultIfEmpty'; export { filter } from './filter';