From 9c018b5f05573ce14bdfd32dcd5db1663d1dff16 Mon Sep 17 00:00:00 2001 From: Mateusz Podlasin Date: Mon, 6 Feb 2017 06:48:18 +0100 Subject: [PATCH] docs(ObservableInput): add ObservableInput and SubscribableOrPromise descriptions Add ObservableInput and SubscribableOrPromise interface descriptions, as well as link these interfaces in type descriptions of operators, so that users always know what kind of parameters they can pass to used methods. --- src/MiscJSDoc.ts | 218 ++++++++++++++++++++++++++++++ src/observable/DeferObservable.ts | 2 +- src/observable/combineLatest.ts | 4 +- src/operator/audit.ts | 2 +- src/operator/combineLatest.ts | 2 +- src/operator/concat.ts | 6 +- src/operator/concatMap.ts | 2 +- src/operator/concatMapTo.ts | 2 +- src/operator/debounce.ts | 2 +- src/operator/exhaustMap.ts | 2 +- src/operator/merge.ts | 4 +- src/operator/mergeMap.ts | 2 +- src/operator/mergeMapTo.ts | 2 +- src/operator/switchMap.ts | 2 +- src/operator/switchMapTo.ts | 2 +- src/operator/throttle.ts | 2 +- src/operator/withLatestFrom.ts | 2 +- 17 files changed, 238 insertions(+), 20 deletions(-) diff --git a/src/MiscJSDoc.ts b/src/MiscJSDoc.ts index d9ca775a61..0104579963 100644 --- a/src/MiscJSDoc.ts +++ b/src/MiscJSDoc.ts @@ -128,3 +128,221 @@ export class ObserverDoc { return void 0; } } + +/** + * `SubscribableOrPromise` interface describes values that behave like either + * Observables or Promises. Every operator that accepts arguments annotated + * with this interface, can be also used with parameters that are not necessarily + * RxJS Observables. + * + * Following types of values might be passed to operators expecting this interface: + * + * ## Observable + * + * RxJS {@link Observable} instance. + * + * ## Observable-like (Subscribable) + * + * This might be any object that has `Symbol.observable` method. This method, + * when called, should return object with `subscribe` method on it, which should + * behave the same as RxJS `Observable.subscribe`. + * + * `Symbol.observable` is part of https://github.com/tc39/proposal-observable proposal. + * Since currently it is not supported natively, and every symbol is equal only to itself, + * you should use https://github.com/blesh/symbol-observable polyfill, when implementing + * custom Observable-likes. + * + * **TypeScript Subscribable interface issue** + * + * Although TypeScript interface claims that Subscribable is an object that has `subscribe` + * method declared directly on it, passing custom objects that have `subscribe` + * method but not `Symbol.observable` method will fail at runtime. Conversely, passing + * objects with `Symbol.observable` but without `subscribe` will fail at compile time + * (if you use TypeScript). + * + * TypeScript has problem supporting interfaces with methods defined as symbol + * properties. To get around that, you should implement `subscribe` directly on + * passed object, and make `Symbol.observable` method simply return `this`. That way + * everything will work as expected, and compiler will not complain. If you really + * do not want to put `subscribe` directly on your object, you will have to type cast + * it to `any`, before passing it to an operator. + * + * When this issue is resolved, Subscribable interface will only permit Observable-like + * objects with `Symbol.observable` defined, no matter if they themselves implement + * `subscribe` method or not. + * + * ## ES6 Promise + * + * Promise can be interpreted as Observable that emits value and completes + * when it is resolved or errors when it is rejected. + * + * ## Promise-like (Thenable) + * + * Promises passed to operators do not have to be native ES6 Promises. + * They can be implementations from popular Promise libraries, polyfills + * or even custom ones. They just need to have `then` method that works + * as the same as ES6 Promise `then`. + * + * @example Use merge and then map with non-RxJS observable + * const nonRxJSObservable = { + * subscribe(observer) { + * observer.next(1000); + * observer.complete(); + * }, + * [Symbol.observable]() { + * return this; + * } + * }; + * + * Rx.Observable.merge(nonRxJSObservable) + * .map(value => "This value is " + value) + * .subscribe(result => console.log(result)); // Logs "This value is 1000" + * + * + * @example Use combineLatest with ES6 Promise + * Rx.Observable.combineLatest(Promise.resolve(5), Promise.resolve(10), Promise.resolve(15)) + * .subscribe( + * value => console.log(value), + * err => {}, + * () => console.log('the end!') + * ); + * // Logs + * // [5, 10, 15] + * // "the end!" + * + * + * @interface + * @name SubscribableOrPromise + * @noimport true + */ +export class SubscribableOrPromiseDoc { + +} + +/** + * `ObservableInput` interface describes all values that are either an + * {@link SubscribableOrPromise} or some kind of collection of values that + * can be transformed to Observable emitting that values. Every operator that + * accepts arguments annotated with this interface, can be also used with + * parameters that are not necessarily RxJS Observables. + * + * `ObservableInput` extends {@link SubscribableOrPromise} with following types: + * + * ## Array + * + * Arrays can be interpreted as observables that emit all values in array one by one, + * from left to right, and then complete immediately. + * + * ## Array-like + * + * Arrays passed to operators do not have to be built-in JavaScript Arrays. They + * can be also, for example, `arguments` property available inside every function, + * [DOM NodeList](https://developer.mozilla.org/pl/docs/Web/API/NodeList), + * or, actually, any object that has `length` property (which is a number) + * and stores values under non-negative (zero and up) integers. + * + * ## ES6 Iterable + * + * Operators will accept both built-in and custom ES6 Iterables, by treating them as + * observables that emit all its values in order of iteration and then complete + * when iteration ends. Note that contrary to arrays, Iterables do not have to + * necessarily be finite, so creating Observables that never complete is possible as well. + * + * Note that you can make iterator an instance of Iterable by having it return itself + * in `Symbol.iterator` method. It means that every operator accepting Iterables accepts, + * though indirectly, iterators themselves as well. All native ES6 iterators are instances + * of Iterable by default, so you do not have to implement their `Symbol.iterator` method + * yourself. + * + * **TypeScript Iterable interface issue** + * + * TypeScript `ObservableInput` interface actually lacks type signature for Iterables, + * because of issues it caused in some projects (see [this issue](https://github.com/ReactiveX/rxjs/issues/2306)). + * If you want to use Iterable as argument for operator, cast it to `any` first. + * Remember of course that, because of casting, you have to yourself ensure that passed + * argument really implements said interface. + * + * + * @example Use merge with arrays + * Rx.Observable.merge([1, 2], [4], [5, 6]) + * .subscribe( + * value => console.log(value), + * err => {}, + * () => console.log('ta dam!') + * ); + * + * // Logs + * // 1 + * // 2 + * // 3 + * // 4 + * // 5 + * // 6 + * // "ta dam!" + * + * + * @example Use merge with array-like + * Rx.Observable.merge({0: 1, 1: 2, length: 2}, {0: 3, length: 1}) + * .subscribe( + * value => console.log(value), + * err => {}, + * () => console.log('nice, huh?') + * ); + * + * // Logs + * // 1 + * // 2 + * // 3 + * // "nice, huh?" + * + * @example Use merge with an Iterable (Map) + * const firstMap = new Map([[1, 'a'], [2, 'b']]); + * const secondMap = new Map([[3, 'c'], [4, 'd']]); + * + * Rx.Observable.merge( + * firstMap, // pass Iterable + * secondMap.values() // pass iterator, which is itself an Iterable + * ).subscribe( + * value => console.log(value), + * err => {}, + * () => console.log('yup!') + * ); + * + * // Logs + * // [1, "a"] + * // [2, "b"] + * // "c" + * // "d" + * // "yup!" + * + * @example Use from with generator (returning infinite iterator) + * // infinite stream of incrementing numbers + * const infinite = function* () { + * let i = 0; + * + * while (true) { + * yield i++; + * } + * }; + * + * Rx.Observable.from(infinite()) + * .take(3) // only take 3, cause this is infinite + * .subscribe( + * value => console.log(value), + * err => {}, + * () => console.log('ta dam!') + * ); + * + * // Logs + * // 0 + * // 1 + * // 2 + * // "ta dam!" + * + * @interface + * @name ObservableInput + * @noimport true + */ +export class ObservableInputDoc { + +} diff --git a/src/observable/DeferObservable.ts b/src/observable/DeferObservable.ts index 17bff114c0..7bf6234cc4 100644 --- a/src/observable/DeferObservable.ts +++ b/src/observable/DeferObservable.ts @@ -47,7 +47,7 @@ export class DeferObservable extends Observable { * * @see {@link create} * - * @param {function(): Observable|Promise} observableFactory The Observable + * @param {function(): SubscribableOrPromise} observableFactory The Observable * factory function to invoke for each Observer that subscribes to the output * Observable. May also return a Promise, which will be converted on the fly * to an Observable. diff --git a/src/observable/combineLatest.ts b/src/observable/combineLatest.ts index 2f152c5d5f..daa903d0c7 100644 --- a/src/observable/combineLatest.ts +++ b/src/observable/combineLatest.ts @@ -61,9 +61,9 @@ export function combineLatest(...observables: Array | (( * @see {@link merge} * @see {@link withLatestFrom} * - * @param {Observable} observable1 An input Observable to combine with the + * @param {ObservableInput} observable1 An input Observable to combine with the * source Observable. - * @param {Observable} observable2 An input Observable to combine with the + * @param {ObservableInput} observable2 An input Observable to combine with the * source Observable. More than one input Observables may be given as argument. * @param {function} [project] An optional function to project the values from * the combined latest values into a new value on the output Observable. diff --git a/src/operator/audit.ts b/src/operator/audit.ts index e9caef0991..41a14bd652 100644 --- a/src/operator/audit.ts +++ b/src/operator/audit.ts @@ -40,7 +40,7 @@ import { subscribeToResult } from '../util/subscribeToResult'; * @see {@link sample} * @see {@link throttle} * - * @param {function(value: T): Observable|Promise} durationSelector A function + * @param {function(value: T): SubscribableOrPromise} durationSelector A function * that receives a value from the source Observable, for computing the silencing * duration, returned as an Observable or a Promise. * @return {Observable} An Observable that performs rate-limiting of diff --git a/src/operator/combineLatest.ts b/src/operator/combineLatest.ts index d06d1dfa24..6c8da16797 100644 --- a/src/operator/combineLatest.ts +++ b/src/operator/combineLatest.ts @@ -58,7 +58,7 @@ export function combineLatest(this: Observable, array: Observab * @see {@link merge} * @see {@link withLatestFrom} * - * @param {Observable} other An input Observable to combine with the source + * @param {ObservableInput} other An input Observable to combine with the source * Observable. More than one input Observables may be given as argument. * @param {function} [project] An optional function to project the values from * the combined latest values into a new value on the output Observable. diff --git a/src/operator/concat.ts b/src/operator/concat.ts index 1e1a32313f..5f387b1310 100644 --- a/src/operator/concat.ts +++ b/src/operator/concat.ts @@ -55,7 +55,7 @@ export function concat(this: Observable, ...observables: Array(...observables: (ObservableInput | ISche * @see {@link concatMap} * @see {@link concatMapTo} * - * @param {Observable} input1 An input Observable to concatenate with others. - * @param {Observable} input2 An input Observable to concatenate with others. + * @param {ObservableInput} input1 An input Observable to concatenate with others. + * @param {ObservableInput} input2 An input Observable to concatenate with others. * More than one input Observables may be given as argument. * @param {Scheduler} [scheduler=null] An optional IScheduler to schedule each * Observable subscription on. diff --git a/src/operator/concatMap.ts b/src/operator/concatMap.ts index 1fdb1807d0..e692efbea8 100644 --- a/src/operator/concatMap.ts +++ b/src/operator/concatMap.ts @@ -47,7 +47,7 @@ export function concatMap(this: Observable, project: (value: T, inde * @see {@link mergeMap} * @see {@link switchMap} * - * @param {function(value: T, ?index: number): Observable} project A function + * @param {function(value: T, ?index: number): ObservableInput} project A function * that, when applied to an item emitted by the source Observable, returns an * Observable. * @param {function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any} [resultSelector] diff --git a/src/operator/concatMapTo.ts b/src/operator/concatMapTo.ts index e1b468c1f2..ac86335a2b 100644 --- a/src/operator/concatMapTo.ts +++ b/src/operator/concatMapTo.ts @@ -46,7 +46,7 @@ export function concatMapTo(this: Observable, observable: Observable * @see {@link mergeMapTo} * @see {@link switchMapTo} * - * @param {Observable} innerObservable An Observable to replace each value from + * @param {ObservableInput} innerObservable An Observable to replace each value from * the source Observable. * @param {function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any} [resultSelector] * A function to produce the value on the output Observable based on the values diff --git a/src/operator/debounce.ts b/src/operator/debounce.ts index 1520fe067f..d19d508d26 100644 --- a/src/operator/debounce.ts +++ b/src/operator/debounce.ts @@ -40,7 +40,7 @@ import { subscribeToResult } from '../util/subscribeToResult'; * @see {@link delayWhen} * @see {@link throttle} * - * @param {function(value: T): Observable|Promise} durationSelector A function + * @param {function(value: T): SubscribableOrPromise} durationSelector A function * that receives a value from the source Observable, for computing the timeout * duration for each source value, returned as an Observable or a Promise. * @return {Observable} An Observable that delays the emissions of the source diff --git a/src/operator/exhaustMap.ts b/src/operator/exhaustMap.ts index 6f65db9ff6..bd50b0e384 100644 --- a/src/operator/exhaustMap.ts +++ b/src/operator/exhaustMap.ts @@ -39,7 +39,7 @@ export function exhaustMap(this: Observable, project: (value: T, ind * @see {@link mergeMap} * @see {@link switchMap} * - * @param {function(value: T, ?index: number): Observable} project A function + * @param {function(value: T, ?index: number): ObservableInput} project A function * that, when applied to an item emitted by the source Observable, returns an * Observable. * @param {function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any} [resultSelector] diff --git a/src/operator/merge.ts b/src/operator/merge.ts index 1a34d10db4..14bbe5d615 100644 --- a/src/operator/merge.ts +++ b/src/operator/merge.ts @@ -56,7 +56,7 @@ export function merge(this: Observable, ...observables: Array(...observables: (ObservableInput | ISched * @see {@link mergeMapTo} * @see {@link mergeScan} * - * @param {...Observable} observables Input Observables to merge together. + * @param {...ObservableInput} observables Input Observables to merge together. * @param {number} [concurrent=Number.POSITIVE_INFINITY] Maximum number of input * Observables being subscribed to concurrently. * @param {Scheduler} [scheduler=null] The IScheduler to use for managing diff --git a/src/operator/mergeMap.ts b/src/operator/mergeMap.ts index 89f64936da..9a274f7c08 100644 --- a/src/operator/mergeMap.ts +++ b/src/operator/mergeMap.ts @@ -49,7 +49,7 @@ export function mergeMap(this: Observable, project: (value: T, index * @see {@link mergeScan} * @see {@link switchMap} * - * @param {function(value: T, ?index: number): Observable} project A function + * @param {function(value: T, ?index: number): ObservableInput} project A function * that, when applied to an item emitted by the source Observable, returns an * Observable. * @param {function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any} [resultSelector] diff --git a/src/operator/mergeMapTo.ts b/src/operator/mergeMapTo.ts index 5fc173e701..0f64215d27 100644 --- a/src/operator/mergeMapTo.ts +++ b/src/operator/mergeMapTo.ts @@ -37,7 +37,7 @@ export function mergeMapTo(this: Observable, observable: ObservableI * @see {@link mergeScan} * @see {@link switchMapTo} * - * @param {Observable} innerObservable An Observable to replace each value from + * @param {ObservableInput} innerObservable An Observable to replace each value from * the source Observable. * @param {function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any} [resultSelector] * A function to produce the value on the output Observable based on the values diff --git a/src/operator/switchMap.ts b/src/operator/switchMap.ts index b789f1dc78..1dc65f3708 100644 --- a/src/operator/switchMap.ts +++ b/src/operator/switchMap.ts @@ -40,7 +40,7 @@ export function switchMap(this: Observable, project: (value: T, inde * @see {@link switch} * @see {@link switchMapTo} * - * @param {function(value: T, ?index: number): Observable} project A function + * @param {function(value: T, ?index: number): ObservableInput} project A function * that, when applied to an item emitted by the source Observable, returns an * Observable. * @param {function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any} [resultSelector] diff --git a/src/operator/switchMapTo.ts b/src/operator/switchMapTo.ts index 80bf3f6158..dcc0348108 100644 --- a/src/operator/switchMapTo.ts +++ b/src/operator/switchMapTo.ts @@ -36,7 +36,7 @@ export function switchMapTo(this: Observable, observable: Observable * @see {@link switchMap} * @see {@link mergeMapTo} * - * @param {Observable} innerObservable An Observable to replace each value from + * @param {ObservableInput} innerObservable An Observable to replace each value from * the source Observable. * @param {function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any} [resultSelector] * A function to produce the value on the output Observable based on the values diff --git a/src/operator/throttle.ts b/src/operator/throttle.ts index fc352ad71b..a3e06488a5 100644 --- a/src/operator/throttle.ts +++ b/src/operator/throttle.ts @@ -37,7 +37,7 @@ import { subscribeToResult } from '../util/subscribeToResult'; * @see {@link sample} * @see {@link throttleTime} * - * @param {function(value: T): Observable|Promise} durationSelector A function + * @param {function(value: T): SubscribableOrPromise} durationSelector A function * that receives a value from the source Observable, for computing the silencing * duration for each source value, returned as an Observable or a Promise. * @return {Observable} An Observable that performs the throttle operation to diff --git a/src/operator/withLatestFrom.ts b/src/operator/withLatestFrom.ts index dc4777b66e..72b2f22408 100644 --- a/src/operator/withLatestFrom.ts +++ b/src/operator/withLatestFrom.ts @@ -47,7 +47,7 @@ export function withLatestFrom(this: Observable, array: ObservableInput * * @see {@link combineLatest} * - * @param {Observable} other An input Observable to combine with the source + * @param {ObservableInput} other An input Observable to combine with the source * Observable. More than one input Observables may be given as argument. * @param {Function} [project] Projection function for combining values * together. Receives all values in order of the Observables passed, where the