Skip to content

Commit

Permalink
feat(map): add higher-order lettable map operator
Browse files Browse the repository at this point in the history
  • Loading branch information
benlesh committed Jun 15, 2017
1 parent 70eaafc commit ce40b2d
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 48 deletions.
14 changes: 11 additions & 3 deletions src/observable/dom/AjaxObservable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { errorObject } from '../../util/errorObject';
import { Observable } from '../../Observable';
import { Subscriber } from '../../Subscriber';
import { TeardownLogic } from '../../Subscription';
import { MapOperator } from '../../operator/map';
import { map } from '../../operators';

export interface AjaxRequest {
url?: string;
Expand Down Expand Up @@ -87,9 +87,17 @@ export function ajaxPatch(url: string, body?: any, headers?: Object): Observable
return new AjaxObservable<AjaxResponse>({ method: 'PATCH', url, body, headers });
};

const mapResponse = map((x: AjaxResponse, index: number) => x.response);

export function ajaxGetJSON<T>(url: string, headers?: Object): Observable<T> {
return new AjaxObservable<AjaxResponse>({ method: 'GET', url, responseType: 'json', headers })
.lift<T>(new MapOperator<AjaxResponse, T>((x: AjaxResponse, index: number): T => x.response, null));
return mapResponse(
new AjaxObservable<AjaxResponse>({
method: 'GET',
url,
responseType: 'json',
headers
})
);
};

/**
Expand Down
47 changes: 2 additions & 45 deletions src/operator/map.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Operator } from '../Operator';
import { Subscriber } from '../Subscriber';
import { map as higherOrderMap } from '../operators';
import { Observable } from '../Observable';

/**
Expand Down Expand Up @@ -36,47 +35,5 @@ import { Observable } from '../Observable';
* @owner Observable
*/
export function map<T, R>(this: Observable<T>, project: (value: T, index: number) => R, thisArg?: any): Observable<R> {
if (typeof project !== 'function') {
throw new TypeError('argument is not a function. Are you looking for `mapTo()`?');
}
return this.lift(new MapOperator(project, thisArg));
}

export class MapOperator<T, R> implements Operator<T, R> {
constructor(private project: (value: T, index: number) => R, private thisArg: any) {
}

call(subscriber: Subscriber<R>, source: any): any {
return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg));
}
}

/**
* We need this JSDoc comment for affecting ESDoc.
* @ignore
* @extends {Ignored}
*/
class MapSubscriber<T, R> extends Subscriber<T> {
count: number = 0;
private thisArg: any;

constructor(destination: Subscriber<R>,
private project: (value: T, index: number) => R,
thisArg: any) {
super(destination);
this.thisArg = thisArg || this;
}

// NOTE: This looks unoptimized, but it's actually purposefully NOT
// using try/catch optimizations.
protected _next(value: T) {
let result: any;
try {
result = this.project.call(this.thisArg, value, this.count++);
} catch (err) {
this.destination.error(err);
return;
}
this.destination.next(result);
}
return higherOrderMap(project, thisArg)(this);
}
1 change: 1 addition & 0 deletions src/operators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { map, OperatorFunction } from './map';
88 changes: 88 additions & 0 deletions src/operators/map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Operator } from '../Operator';
import { Subscriber } from '../Subscriber';
import { Observable } from '../Observable';

export interface OperatorFunction<T, R> {
(source: Observable<T>): Observable<R>;
}

/**
* Applies a given `project` function to each value emitted by the source
* Observable, and emits the resulting values as an Observable.
*
* <span class="informal">Like [Array.prototype.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map),
* it passes each source value through a transformation function to get
* corresponding output values.</span>
*
* <img src="./img/map.png" width="100%">
*
* Similar to the well known `Array.prototype.map` function, this operator
* applies a projection to each value and emits that projection in the output
* Observable.
*
* @example <caption>Map every click to the clientX position of that click</caption>
* var clicks = Rx.Observable.fromEvent(document, 'click');
* var positions = clicks.map(ev => ev.clientX);
* positions.subscribe(x => console.log(x));
*
* @see {@link mapTo}
* @see {@link pluck}
*
* @param {function(value: T, index: number): R} project The function to apply
* to each `value` emitted by the source Observable. The `index` parameter is
* the number `i` for the i-th emission that has happened since the
* subscription, starting from the number `0`.
* @param {any} [thisArg] An optional argument to define what `this` is in the
* `project` function.
* @return {Observable<R>} An Observable that emits the values from the source
* Observable transformed by the given `project` function.
* @method map
* @owner Observable
*/
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
return function mapOperation(source: Observable<T>): Observable<R> {
if (typeof project !== 'function') {
throw new TypeError('argument is not a function. Are you looking for `mapTo()`?');
}
return source.lift(new MapOperator(project, thisArg));
};
}

export class MapOperator<T, R> implements Operator<T, R> {
constructor(private project: (value: T, index: number) => R, private thisArg: any) {
}

call(subscriber: Subscriber<R>, source: any): any {
return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg));
}
}

/**
* We need this JSDoc comment for affecting ESDoc.
* @ignore
* @extends {Ignored}
*/
class MapSubscriber<T, R> extends Subscriber<T> {
count: number = 0;
private thisArg: any;

constructor(destination: Subscriber<R>,
private project: (value: T, index: number) => R,
thisArg: any) {
super(destination);
this.thisArg = thisArg || this;
}

// NOTE: This looks unoptimized, but it's actually purposefully NOT
// using try/catch optimizations.
protected _next(value: T) {
let result: any;
try {
result = this.project.call(this.thisArg, value, this.count++);
} catch (err) {
this.destination.error(err);
return;
}
this.destination.next(result);
}
}

0 comments on commit ce40b2d

Please sign in to comment.