diff --git a/spec/observables/from-spec.js b/spec/observables/from-spec.js index 42f6e578f5..8cb63c940d 100644 --- a/spec/observables/from-spec.js +++ b/spec/observables/from-spec.js @@ -13,6 +13,67 @@ describe('Observable.from', function () { }, null, done); }, 300); + it('should handle an ArrayLike', function (done) { + var arrayLike = { + length: 3, + 0: 1, + 1: 2, + 2: 3 + }; + var expected = [1, 2, 3]; + var i = 0; + Observable.from(arrayLike).subscribe(function (x) { + expect(x).toBe(expected[i++]); + }, null, done); + }, 300); + + it('should handle an ArrayLike from arguments', function (done) { + function makeArrayLike() { + var expected = [1, 2, 3]; + var i = 0; + + Observable.from(arguments).subscribe(function (x) { + expect(x).toBe(expected[i++]); + }, null, done); + } + + makeArrayLike(1, 2, 3); + }, 300); + + it('should handle an ArrayLike with a mapFn', function (done) { + var arrayLike = { + length: 3, + 0: 1, + 1: 2, + 2: 3 + }; + var expected = [1, 1, 1]; + var i = 0; + var mapFn = function (v, k) { + return v - k; + }; + Observable.from(arrayLike, mapFn).subscribe(function (x) { + expect(x).toBe(expected[i++]); + }, null, done); + }, 300); + + it('should handle an ArrayLike with a thisArg', function (done) { + var arrayLike = { + length: 3, + 0: 1, + 1: 2, + 2: 3 + }; + var expected = [123, 123, 123]; + var i = 0; + var mapFn = function (x, y) { + return this.thing; + }; + Observable.from(arrayLike, mapFn, {thing: 123}).subscribe(function (x) { + expect(x).toBe(expected[i++]); + }, null, done); + }); + it('should handle a promise', function (done) { var promise = Promise.resolve('pinky swear'); diff --git a/src/observable/ArrayLikeObservable.ts b/src/observable/ArrayLikeObservable.ts new file mode 100644 index 0000000000..5717ec2c1a --- /dev/null +++ b/src/observable/ArrayLikeObservable.ts @@ -0,0 +1,74 @@ +import {Scheduler} from '../Scheduler'; +import {Observable} from '../Observable'; +import {ScalarObservable} from './ScalarObservable'; +import {EmptyObservable} from './EmptyObservable'; +import {Subscriber} from '../Subscriber'; +import {Subscription} from '../Subscription'; + +export class ArrayLikeObservable extends Observable { + + private mapFn: (x: any, y: number) => T; + + static create(arrayLike: ArrayLike, mapFn: (x: any, y: number) => T, thisArg: any, scheduler?: Scheduler): Observable { + const length = arrayLike.length; + if (length === 0) { + return new EmptyObservable(); + } else if (length === 1 && !mapFn) { + return new ScalarObservable(arrayLike[0], scheduler); + } else { + return new ArrayLikeObservable(arrayLike, mapFn, thisArg, scheduler); + } + } + + static dispatch(state: any) { + const { arrayLike, index, length, mapFn, subscriber } = state; + + if (subscriber.isUnsubscribed) { + return; + } + + if (index >= length) { + subscriber.complete(); + return; + } + + const result = mapFn ? mapFn(arrayLike[index], index) : arrayLike[index]; + subscriber.next(result); + + state.index = index + 1; + + ( this).schedule(state); + } + + // value used if Array has one value and _isScalar + private value: any; + + constructor(private arrayLike: ArrayLike, mapFn: (x: any, y: number) => T, thisArg: any, private scheduler?: Scheduler) { + super(); + if (!mapFn && !scheduler && arrayLike.length === 1) { + this._isScalar = true; + this.value = arrayLike[0]; + } + if (mapFn) { + this.mapFn = mapFn.bind(thisArg); + } + } + + protected _subscribe(subscriber: Subscriber): Subscription | Function | void { + let index = 0; + const { arrayLike, mapFn, scheduler } = this; + const length = arrayLike.length; + + if (scheduler) { + return scheduler.schedule(ArrayLikeObservable.dispatch, 0, { + arrayLike, index, length, mapFn, subscriber + }); + } else { + for (let i = 0; i < length && !subscriber.isUnsubscribed; i++) { + const result = mapFn ? mapFn(arrayLike[i], i) : arrayLike[i]; + subscriber.next(result); + } + subscriber.complete(); + } + } +} diff --git a/src/observable/FromObservable.ts b/src/observable/FromObservable.ts index afb0a40509..be9a0db618 100644 --- a/src/observable/FromObservable.ts +++ b/src/observable/FromObservable.ts @@ -1,8 +1,11 @@ import {isArray} from '../util/isArray'; +import {isFunction} from '../util/isFunction'; import {isPromise} from '../util/isPromise'; +import {isScheduler} from '../util/isScheduler'; import {PromiseObservable} from './PromiseObservable'; import {IteratorObservable} from'./IteratorObservable'; import {ArrayObservable} from './ArrayObservable'; +import {ArrayLikeObservable} from './ArrayLikeObservable'; import {Scheduler} from '../Scheduler'; import {SymbolShim} from '../util/SymbolShim'; @@ -10,24 +13,37 @@ import {Observable} from '../Observable'; import {Subscriber} from '../Subscriber'; import {ObserveOnSubscriber} from '../operator/observeOn'; +const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number'); + export class FromObservable extends Observable { constructor(private ish: Observable | Promise | Iterator | ArrayLike, private scheduler: Scheduler) { super(null); } - static create(ish: any, scheduler: Scheduler = null): Observable { + static create(ish: any, mapFnOrScheduler: Scheduler | ((x: any, y: number) => T), thisArg?: any, lastScheduler?: Scheduler): Observable { + let scheduler: Scheduler = null; + let mapFn: (x: number, y: any) => T = null; + if (isFunction(mapFnOrScheduler)) { + scheduler = lastScheduler || null; + mapFn = <(x: number, y: any) => T> mapFnOrScheduler; + } else if (isScheduler(scheduler)) { + scheduler = mapFnOrScheduler; + } + if (ish != null) { if (typeof ish[SymbolShim.observable] === 'function') { if (ish instanceof Observable && !scheduler) { return ish; } return new FromObservable(ish, scheduler); - } if (isArray(ish)) { + } else if (isArray(ish)) { return new ArrayObservable(ish, scheduler); } else if (isPromise(ish)) { return new PromiseObservable(ish, scheduler); } else if (typeof ish[SymbolShim.iterator] === 'function' || typeof ish === 'string') { return new IteratorObservable(ish, null, null, scheduler); + } else if (isArrayLike(ish)) { + return new ArrayLikeObservable(ish, mapFn, thisArg, scheduler); } }