diff --git a/spec/operators/single-spec.ts b/spec/operators/single-spec.ts index 033abdb860..db7b720f3f 100644 --- a/spec/operators/single-spec.ts +++ b/spec/operators/single-spec.ts @@ -1,173 +1,331 @@ import { expect } from 'chai'; -import { hot, expectObservable, expectSubscriptions } from '../helpers/marble-testing'; import { single, mergeMap, tap } from 'rxjs/operators'; -import { of, EmptyError } from 'rxjs'; - -declare function asDiagram(arg: string): Function; +import { of, EmptyError, SequenceError, NotFoundError } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; +import { assertDeepEquals } from '../helpers/test-helper'; /** @test {single} */ describe('single operator', () => { - asDiagram('single')('should raise error from empty predicate if observable emits multiple time', () => { - const e1 = hot('--a--b--c--|'); - const e1subs = '^ ! '; - const expected = '-----# '; - const errorMsg = 'Sequence contains more than one element'; + let rxTest: TestScheduler; - expectObservable(e1.pipe(single())).toBe(expected, null, errorMsg); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + beforeEach(() => { + rxTest = new TestScheduler(assertDeepEquals); }); - it('should raise error from empty predicate if observable does not emit', () => { - const e1 = hot('--a--^--|'); - const e1subs = '^ !'; - const expected = '---#'; + it('should raise error from empty predicate if observable emits multiple time', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c--|'); + const e1subs = ' ^----! '; + const expected = '-----# '; - expectObservable(e1.pipe(single())).toBe(expected, null, new EmptyError()); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectObservable(e1.pipe(single())).toBe(expected, null, new SequenceError('Too many matching values')); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); }); - it('should return only element from empty predicate if observable emits only once', () => { - const e1 = hot('--a--|'); - const e1subs = '^ !'; - const expected = '-----(a|)'; + it('should raise error from empty predicate if observable does not emit', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot('--a--^--|'); + const e1subs = ' ^--!'; + const expected = ' ---#'; + + expectObservable(e1.pipe(single())).toBe(expected, null, new EmptyError()); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + }); - expectObservable(e1.pipe(single())).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + it('should return only element from empty predicate if observable emits only once', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--|'); + const e1subs = ' ^----!'; + const expected = '-----(a|)'; + + expectObservable(e1.pipe(single())).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); }); it('should allow unsubscribing explicitly and early', () => { - const e1 = hot('--a--b--c--|'); - const unsub = ' ! '; - const e1subs = '^ ! '; - const expected = '---- '; - - expectObservable(e1.pipe(single()), unsub).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c--|'); + const unsub = ' ----! '; + const e1subs = ' ^---! '; + const expected = '------------'; + + expectObservable(e1.pipe(single()), unsub).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); }); it('should not break unsubscription chains when result is unsubscribed explicitly', () => { - const e1 = hot('--a--b--c--|'); - const e1subs = '^ ! '; - const expected = '---- '; - const unsub = ' ! '; + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c--|'); + const e1subs = ' ^--! '; + const expected = '---- '; + const unsub = ' ---! '; + + const result = e1.pipe( + mergeMap(x => of(x)), + single(), + mergeMap(x => of(x)) + ); + + expectObservable(result, unsub).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + }); - const result = e1.pipe( - mergeMap((x: string) => of(x)), - single(), - mergeMap((x: string) => of(x)) - ); + it('should raise error from empty predicate if observable emits error', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b^--#'); + const e1subs = ' ^--!'; + const expected = ' ---#'; + + expectObservable(e1.pipe(single())).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + }); - expectObservable(result, unsub).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + it('should raise error from predicate if observable emits error', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot('--a--b^--#'); + const e1subs = ' ^--!'; + const expected = ' ---#'; + + expectObservable(e1.pipe(single(v => v === 'c'))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); }); - it('should raise error from empty predicate if observable emits error', () => { - const e1 = hot('--a--b^--#'); - const e1subs = '^ !'; - const expected = '---#'; + it('should raise error if predicate throws error', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c--d--|'); + const e1subs = ' ^----------! '; + const expected = '-----------# '; + + expectObservable( + e1.pipe( + single(v => { + if (v !== 'd') { + return false; + } + throw 'error'; + }) + ) + ).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + }); - expectObservable(e1.pipe(single())).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + it('should return element from predicate if observable have single matching element', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c--|'); + const e1subs = ' ^----------!'; + const expected = '-----------(b|)'; + + expectObservable(e1.pipe(single(v => v === 'b'))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); }); - it('should raise error from predicate if observable emits error', () => { - const e1 = hot('--a--b^--#'); - const e1subs = '^ !'; - const expected = '---#'; + it('should raise error from predicate if observable have multiple matching element', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--a--b--b--|'); + const e1subs = ' ^----------! '; + const expected = '-----------# '; + + expectObservable(e1.pipe(single(v => v === 'b'))).toBe(expected, null, new SequenceError('Too many matching values')); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + }); - const predicate = function (value: string) { - return value === 'c'; - }; + it('should raise error from predicate if observable does not emit', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot('--a--^--|'); + const e1subs = ' ^--!'; + const expected = ' ---#'; + + expectObservable(e1.pipe(single(v => v === 'a'))).toBe(expected, null, new EmptyError()); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + }); - expectObservable(e1.pipe(single(predicate))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + it('should return undefined from predicate if observable does not contain matching element', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c--|'); + const e1subs = ' ^----------!'; + const expected = '-----------#'; + + expectObservable(e1.pipe(single(v => v === 'x'))).toBe(expected, undefined, new NotFoundError('No matching values')); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); }); - it('should raise error if predicate throws error', () => { - const e1 = hot('--a--b--c--d--|'); - const e1subs = '^ ! '; - const expected = '-----------# '; + it('should call predicate with indices starting at 0', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c--|'); + const e1subs = ' ^----------!'; + const expected = '-----------(b|)'; + + let indices: number[] = []; + const predicate = function(value: string, index: number) { + indices.push(index); + return value === 'b'; + }; + + expectObservable( + e1.pipe( + single(predicate), + tap(null, null, () => { + expect(indices).to.deep.equal([0, 1, 2]); + }) + ) + ).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + }); - const predicate = function (value: string) { - if (value !== 'd') { - return false; - } - throw 'error'; - }; + it('should error for synchronous empty observables when no arguments are provided', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const source = cold('|'); + const expected = ' #'; + const subs = [' (^!)']; + const result = source.pipe(single()); - expectObservable(e1.pipe(single(predicate))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectObservable(result).toBe(expected, undefined, new EmptyError()); + expectSubscriptions(source.subscriptions).toBe(subs); + }); }); - it('should return element from predicate if observable have single matching element', () => { - const e1 = hot('--a--b--c--|'); - const e1subs = '^ !'; - const expected = '-----------(b|)'; + it('should error for async empty observables when no arguments are provided', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const source = cold('-------|'); + const expected = ' -------#'; + const subs = [' ^------!']; + const result = source.pipe(single()); - const predicate = function (value: string) { - return value === 'b'; - }; + expectObservable(result).toBe(expected, undefined, new EmptyError()); + expectSubscriptions(source.subscriptions).toBe(subs); + }); + }); + + it('should error for hot observables that do not emit while active when no arguments are provided', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const source = hot('--a--b--^----|'); + const expected = ' -----#'; + const subs = [' ^----!']; + const result = source.pipe(single()); - expectObservable(e1.pipe(single(predicate))).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectObservable(result).toBe(expected, undefined, new EmptyError()); + expectSubscriptions(source.subscriptions).toBe(subs); + }); }); - it('should raise error from predicate if observable have multiple matching element', () => { - const e1 = hot('--a--b--a--b--b--|'); - const e1subs = '^ ! '; - const expected = '-----------# '; + it('should error for synchronous empty observables when predicate never passes', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const source = cold('|'); + const expected = ' #'; + const subs = [' (^!)']; + const result = source.pipe(single(() => false)); - const predicate = function (value: string) { - return value === 'b'; - }; + expectObservable(result).toBe(expected, undefined, new EmptyError()); + expectSubscriptions(source.subscriptions).toBe(subs); + }); + }); + + it('should error for async empty observables when predicate never passes', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const source = cold('-------|'); + const expected = ' -------#'; + const subs = [' ^------!']; + const result = source.pipe(single(() => false)); - expectObservable(e1.pipe(single(predicate))).toBe(expected, null, 'Sequence contains more than one element'); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectObservable(result).toBe(expected, undefined, new EmptyError()); + expectSubscriptions(source.subscriptions).toBe(subs); + }); }); - it('should raise error from predicate if observable does not emit', () => { - const e1 = hot('--a--^--|'); - const e1subs = '^ !'; - const expected = '---#'; + it('should error for hot observables that do not emit while active when predicate never passes', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const source = hot('--a--b--^----|'); + const expected = ' -----#'; + const subs = [' ^----!']; + const result = source.pipe(single(() => false)); + + expectObservable(result).toBe(expected, undefined, new EmptyError()); + expectSubscriptions(source.subscriptions).toBe(subs); + }); + }); - const predicate = function (value: string) { - return value === 'a'; - }; + it('should error for synchronous observables that emit when predicate never passes', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const source = cold('(a|)'); + const expected = ' #'; + const subs = [' (^!)']; + const result = source.pipe(single(() => false)); - expectObservable(e1.pipe(single(predicate))).toBe(expected, null, new EmptyError()); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectObservable(result).toBe(expected, undefined, new NotFoundError('No matching values')); + expectSubscriptions(source.subscriptions).toBe(subs); + }); }); - it('should return undefined from predicate if observable does not contain matching element', () => { - const e1 = hot('--a--b--c--|'); - const e1subs = '^ !'; - const expected = '-----------(z|)'; + it('should error for async observables that emit when predicate never passes', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const source = cold('--a--b-|'); + const expected = ' -------#'; + const subs = [' ^------!']; + const result = source.pipe(single(() => false)); + + expectObservable(result).toBe(expected, undefined, new NotFoundError('No matching values')); + expectSubscriptions(source.subscriptions).toBe(subs); + }); + }); - const predicate = function (value: string) { - return value === 'x'; - }; + it('should error for hot observables that emit while active when predicate never passes', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const source = hot('--a--b--^--c--d--|'); + const expected = ' ---------#'; + const subs = [' ^--------!']; + const result = source.pipe(single(() => false)); - expectObservable(e1.pipe(single(predicate))).toBe(expected, {z: undefined}); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectObservable(result).toBe(expected, undefined, new NotFoundError('No matching values')); + expectSubscriptions(source.subscriptions).toBe(subs); + }); }); - it('should call predicate with indices starting at 0', () => { - const e1 = hot('--a--b--c--|'); - const e1subs = '^ !'; - const expected = '-----------(b|)'; - - let indices: number[] = []; - const predicate = function(value: string, index: number) { - indices.push(index); - return value === 'b'; - }; - - expectObservable(e1.pipe( - single(predicate), - tap(null, null, () => { - expect(indices).to.deep.equal([0, 1, 2]); - })) - ).toBe(expected); - expectSubscriptions(e1.subscriptions).toBe(e1subs); + it('should error for synchronous observables when the predicate passes more than once', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const source = cold('(axbxc|)'); + const expected = ' #'; + const subs = [' (^!)']; + const result = source.pipe(single(v => v === 'x')); + + expectObservable(result).toBe(expected, undefined, new SequenceError('Too many matching values')); + expectSubscriptions(source.subscriptions).toBe(subs); + }); + }); + + it('should error for async observables that emit when the predicate passes more than once', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const source = cold('--a-x-b-x-c-|'); + const expected = ' --------#'; + const subs = [' ^-------!']; + const result = source.pipe(single(v => v === 'x')); + + expectObservable(result).toBe(expected, undefined, new SequenceError('Too many matching values')); + expectSubscriptions(source.subscriptions).toBe(subs); + }); + }); + + it('should error for hot observables that emit while active when the predicate passes more than once', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const source = hot('--a--b--^--c--x--d--x--|'); + const expected = ' ------------#'; + const subs = [' ^-----------!']; + const result = source.pipe(single(v => v === 'x')); + + expectObservable(result).toBe(expected, undefined, new SequenceError('Too many matching values')); + expectSubscriptions(source.subscriptions).toBe(subs); + }); }); }); diff --git a/src/index.ts b/src/index.ts index 80f5c4f38b..bafa33eeec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,9 +40,11 @@ export { firstValueFrom } from './internal/firstValueFrom'; /* Error types */ export { ArgumentOutOfRangeError } from './internal/util/ArgumentOutOfRangeError'; export { EmptyError } from './internal/util/EmptyError'; +export { NotFoundError } from './internal/util/NotFoundError'; export { ObjectUnsubscribedError } from './internal/util/ObjectUnsubscribedError'; -export { UnsubscriptionError } from './internal/util/UnsubscriptionError'; +export { SequenceError } from './internal/util/SequenceError'; export { TimeoutError } from './internal/util/TimeoutError'; +export { UnsubscriptionError } from './internal/util/UnsubscriptionError'; /* Static observable creation exports */ export { bindCallback } from './internal/observable/bindCallback'; diff --git a/src/internal/operators/single.ts b/src/internal/operators/single.ts index cc2eab6be5..cf6c28cb81 100644 --- a/src/internal/operators/single.ts +++ b/src/internal/operators/single.ts @@ -1,40 +1,77 @@ import { Observable } from '../Observable'; -import { Operator } from '../Operator'; import { Subscriber } from '../Subscriber'; import { EmptyError } from '../util/EmptyError'; -import { Observer, MonoTypeOperatorFunction, TeardownLogic } from '../types'; +import { MonoTypeOperatorFunction } from '../types'; +import { SequenceError } from '../util/SequenceError'; +import { NotFoundError } from '../util/NotFoundError'; + +const defaultPredicate = () => true; /** - * Returns an Observable that emits the single item emitted by the source Observable that matches a specified - * predicate, if that Observable emits one such item. If the source Observable emits more than one such item or no - * items, notify of an IllegalArgumentException or NoSuchElementException respectively. If the source Observable - * emits items but none match the specified predicate then `undefined` is emitted. + * Returns an observable that asserts that only one value is + * emitted from the observable that matches the predicate. If no + * predicate is provided, then it will assert that the observable + * only emits one value. + * + * In the event that the observable is empty, it will throw an + * {@link EmptyError}. * - * Like {@link first}, but emit with error notification if there is more than one value. - * ![](single.png) + * In the event that two values are found that match the predicate, + * or when there are two values emitted and no predicate, it will + * throw a {@link SequenceError} + * + * In the event that no values match the predicate, if one is provided, + * it will throw a {@link NotFoundError} * * ## Example - * emits 'error' - * ```ts - * import { range } from 'rxjs'; - * import { single } from 'rxjs/operators'; * - * const numbers = range(1,5).pipe(single()); - * numbers.subscribe(x => console.log('never get called'), e => console.log('error')); - * // result - * // 'error' - * ``` + * Expect only name beginning with 'B': * - * emits 'undefined' * ```ts - * import { range } from 'rxjs'; + * import { of } from 'rxjs'; * import { single } from 'rxjs/operators'; * - * const numbers = range(1,5).pipe(single(x => x === 10)); - * numbers.subscribe(x => console.log(x)); - * // result - * // 'undefined' + * const source1 = of( + * { name: 'Ben' }, + * { name: 'Tracy' }, + * { name: 'Laney' }, + * { name: 'Lily' } + * ); + * + * source1.pipe( + * single(x => x.name.startsWith('B')) + * ) + * .subscribe(x => console.log(x)); + * // Emits "Ben" + * + * + * const source2 = of( + * { name: 'Ben' }, + * { name: 'Tracy' }, + * { name: 'Bradley' }, + * { name: 'Lincoln' } + * ); + * + * source2.pipe( + * single(x => x.name.startsWith('B')) + * ) + * .subscribe(x => console.log(x)); + * // Error emitted: SequenceError('Too many values match') + * + * + * const source3 = of( + * { name: 'Laney' }, + * { name: 'Tracy' }, + * { name: 'Lily' }, + * { name: 'Lincoln' } + * ); + * + * source3.pipe( + * single(x => x.name.startsWith('B')) + * ) + * .subscribe(x => console.log(x)); + * // Error emitted: NotFoundError('No values match') * ``` * * @see {@link first} @@ -42,81 +79,57 @@ import { Observer, MonoTypeOperatorFunction, TeardownLogic } from '../types'; * @see {@link findIndex} * @see {@link elementAt} * - * @throws {EmptyError} Delivers an EmptyError to the Observer's `error` + * @throws {NotFoundError} Delivers an NotFoundError to the Observer's `error` * callback if the Observable completes before any `next` notification was sent. + * @throws {SequenceError} Delivers a SequenceError if more than one value is emitted that matches the + * provided predicate. If no predicate is provided, will deliver a SequenceError if more + * that one value comes from the source * @param {Function} predicate - A predicate function to evaluate items emitted by the source Observable. * @return {Observable} An Observable that emits the single item emitted by the source Observable that matches * the predicate or `undefined` when no items match. - * - * @name single */ -export function single(predicate?: (value: T, index: number, source: Observable) => boolean): MonoTypeOperatorFunction { - return (source: Observable) => source.lift(new SingleOperator(predicate, source)); -} - -class SingleOperator implements Operator { - constructor(private predicate: ((value: T, index: number, source: Observable) => boolean) | undefined, - private source: Observable) { - } - - call(subscriber: Subscriber, source: any): TeardownLogic { - return source.subscribe(new SingleSubscriber(subscriber, this.predicate, this.source)); - } +export function single( + predicate: (value: T, index: number, source: Observable) => boolean = defaultPredicate +): MonoTypeOperatorFunction { + return (source: Observable) => source.lift(singleOperator(predicate)); } -/** - * We need this JSDoc comment for affecting ESDoc. - * @ignore - * @extends {Ignored} - */ -class SingleSubscriber extends Subscriber { - private seenValue: boolean = false; - private singleValue: T | undefined; - private index: number = 0; - - constructor(destination: Observer, - private predicate: ((value: T, index: number, source: Observable) => boolean) | undefined, - private source: Observable) { - super(destination); - } - - private applySingleValue(value: T): void { - if (this.seenValue) { - this.destination.error('Sequence contains more than one element'); - } else { - this.seenValue = true; - this.singleValue = value; - } - } - - protected _next(value: T): void { - const index = this.index++; - - if (this.predicate) { - this.tryNext(value, index); - } else { - this.applySingleValue(value); - } - } - - private tryNext(value: T, index: number): void { - try { - if (this.predicate!(value, index, this.source)) { - this.applySingleValue(value); - } - } catch (err) { - this.destination.error(err); - } - } - - protected _complete(): void { - const destination = this.destination; +function singleOperator(predicate: (value: T, index: number, source: Observable) => boolean) { + return function(this: Subscriber, source: Observable) { + let _hasValue = false; + let _seenValue = false; + let _value: T; + let _i = 0; + const _destination = this; - if (this.index > 0) { - destination.next(this.seenValue ? this.singleValue : undefined); - destination.complete(); - } else { - destination.error(new EmptyError); - } - } + return source.subscribe({ + next: value => { + _seenValue = true; + let match = false; + try { + match = predicate(value, _i++, source); + } catch (err) { + _destination.error(err); + return; + } + if (match) { + if (_hasValue) { + _destination.error(new SequenceError('Too many matching values')); + } else { + _hasValue = true; + _value = value; + } + } + }, + error: err => _destination.error(err), + complete: () => { + if (_hasValue) { + _destination.next(_value); + _destination.complete(); + } else { + _destination.error(_seenValue ? new NotFoundError('No matching values') : new EmptyError()); + } + }, + }); + }; } diff --git a/src/internal/util/NotFoundError.ts b/src/internal/util/NotFoundError.ts new file mode 100644 index 0000000000..f97c0ce8ed --- /dev/null +++ b/src/internal/util/NotFoundError.ts @@ -0,0 +1,29 @@ +export interface NotFoundError extends Error { +} + +export interface NotFoundErrorCtor { + new(message: string): NotFoundError; +} + +const NotFoundErrorImpl = (() => { + function NotFoundErrorImpl(this: Error, message: string) { + Error.call(this); + this.message = message; + this.name = 'NotFoundError'; + return this; + } + + NotFoundErrorImpl.prototype = Object.create(Error.prototype); + + return NotFoundErrorImpl; +})(); + +/** + * An error thrown when a value or values are missing from an + * observable sequence. + * + * @see {@link operators/single} + * + * @class NotFoundError + */ +export const NotFoundError: NotFoundErrorCtor = NotFoundErrorImpl as any; diff --git a/src/internal/util/SequenceError.ts b/src/internal/util/SequenceError.ts new file mode 100644 index 0000000000..01379d7e71 --- /dev/null +++ b/src/internal/util/SequenceError.ts @@ -0,0 +1,29 @@ +export interface SequenceError extends Error { +} + +export interface SequenceErrorCtor { + new(message: string): SequenceError; +} + +const SequenceErrorImpl = (() => { + function SequenceErrorImpl(this: Error, message: string) { + Error.call(this); + this.message = message; + this.name = 'SequenceError'; + return this; + } + + SequenceErrorImpl.prototype = Object.create(Error.prototype); + + return SequenceErrorImpl; +})(); + +/** + * An error thrown when something is wrong with the sequence of + * values arriving on the observable. + * + * @see {@link operators/single} + * + * @class SequenceError + */ +export const SequenceError: SequenceErrorCtor = SequenceErrorImpl as any;