Skip to content

Commit ce40b2d

Browse files
committed
feat(map): add higher-order lettable map operator
1 parent 70eaafc commit ce40b2d

File tree

4 files changed

+102
-48
lines changed

4 files changed

+102
-48
lines changed

src/observable/dom/AjaxObservable.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { errorObject } from '../../util/errorObject';
44
import { Observable } from '../../Observable';
55
import { Subscriber } from '../../Subscriber';
66
import { TeardownLogic } from '../../Subscription';
7-
import { MapOperator } from '../../operator/map';
7+
import { map } from '../../operators';
88

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

90+
const mapResponse = map((x: AjaxResponse, index: number) => x.response);
91+
9092
export function ajaxGetJSON<T>(url: string, headers?: Object): Observable<T> {
91-
return new AjaxObservable<AjaxResponse>({ method: 'GET', url, responseType: 'json', headers })
92-
.lift<T>(new MapOperator<AjaxResponse, T>((x: AjaxResponse, index: number): T => x.response, null));
93+
return mapResponse(
94+
new AjaxObservable<AjaxResponse>({
95+
method: 'GET',
96+
url,
97+
responseType: 'json',
98+
headers
99+
})
100+
);
93101
};
94102

95103
/**

src/operator/map.ts

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { Operator } from '../Operator';
2-
import { Subscriber } from '../Subscriber';
1+
import { map as higherOrderMap } from '../operators';
32
import { Observable } from '../Observable';
43

54
/**
@@ -36,47 +35,5 @@ import { Observable } from '../Observable';
3635
* @owner Observable
3736
*/
3837
export function map<T, R>(this: Observable<T>, project: (value: T, index: number) => R, thisArg?: any): Observable<R> {
39-
if (typeof project !== 'function') {
40-
throw new TypeError('argument is not a function. Are you looking for `mapTo()`?');
41-
}
42-
return this.lift(new MapOperator(project, thisArg));
43-
}
44-
45-
export class MapOperator<T, R> implements Operator<T, R> {
46-
constructor(private project: (value: T, index: number) => R, private thisArg: any) {
47-
}
48-
49-
call(subscriber: Subscriber<R>, source: any): any {
50-
return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg));
51-
}
52-
}
53-
54-
/**
55-
* We need this JSDoc comment for affecting ESDoc.
56-
* @ignore
57-
* @extends {Ignored}
58-
*/
59-
class MapSubscriber<T, R> extends Subscriber<T> {
60-
count: number = 0;
61-
private thisArg: any;
62-
63-
constructor(destination: Subscriber<R>,
64-
private project: (value: T, index: number) => R,
65-
thisArg: any) {
66-
super(destination);
67-
this.thisArg = thisArg || this;
68-
}
69-
70-
// NOTE: This looks unoptimized, but it's actually purposefully NOT
71-
// using try/catch optimizations.
72-
protected _next(value: T) {
73-
let result: any;
74-
try {
75-
result = this.project.call(this.thisArg, value, this.count++);
76-
} catch (err) {
77-
this.destination.error(err);
78-
return;
79-
}
80-
this.destination.next(result);
81-
}
38+
return higherOrderMap(project, thisArg)(this);
8239
}

src/operators/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { map, OperatorFunction } from './map';

src/operators/map.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Operator } from '../Operator';
2+
import { Subscriber } from '../Subscriber';
3+
import { Observable } from '../Observable';
4+
5+
export interface OperatorFunction<T, R> {
6+
(source: Observable<T>): Observable<R>;
7+
}
8+
9+
/**
10+
* Applies a given `project` function to each value emitted by the source
11+
* Observable, and emits the resulting values as an Observable.
12+
*
13+
* <span class="informal">Like [Array.prototype.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map),
14+
* it passes each source value through a transformation function to get
15+
* corresponding output values.</span>
16+
*
17+
* <img src="./img/map.png" width="100%">
18+
*
19+
* Similar to the well known `Array.prototype.map` function, this operator
20+
* applies a projection to each value and emits that projection in the output
21+
* Observable.
22+
*
23+
* @example <caption>Map every click to the clientX position of that click</caption>
24+
* var clicks = Rx.Observable.fromEvent(document, 'click');
25+
* var positions = clicks.map(ev => ev.clientX);
26+
* positions.subscribe(x => console.log(x));
27+
*
28+
* @see {@link mapTo}
29+
* @see {@link pluck}
30+
*
31+
* @param {function(value: T, index: number): R} project The function to apply
32+
* to each `value` emitted by the source Observable. The `index` parameter is
33+
* the number `i` for the i-th emission that has happened since the
34+
* subscription, starting from the number `0`.
35+
* @param {any} [thisArg] An optional argument to define what `this` is in the
36+
* `project` function.
37+
* @return {Observable<R>} An Observable that emits the values from the source
38+
* Observable transformed by the given `project` function.
39+
* @method map
40+
* @owner Observable
41+
*/
42+
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
43+
return function mapOperation(source: Observable<T>): Observable<R> {
44+
if (typeof project !== 'function') {
45+
throw new TypeError('argument is not a function. Are you looking for `mapTo()`?');
46+
}
47+
return source.lift(new MapOperator(project, thisArg));
48+
};
49+
}
50+
51+
export class MapOperator<T, R> implements Operator<T, R> {
52+
constructor(private project: (value: T, index: number) => R, private thisArg: any) {
53+
}
54+
55+
call(subscriber: Subscriber<R>, source: any): any {
56+
return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg));
57+
}
58+
}
59+
60+
/**
61+
* We need this JSDoc comment for affecting ESDoc.
62+
* @ignore
63+
* @extends {Ignored}
64+
*/
65+
class MapSubscriber<T, R> extends Subscriber<T> {
66+
count: number = 0;
67+
private thisArg: any;
68+
69+
constructor(destination: Subscriber<R>,
70+
private project: (value: T, index: number) => R,
71+
thisArg: any) {
72+
super(destination);
73+
this.thisArg = thisArg || this;
74+
}
75+
76+
// NOTE: This looks unoptimized, but it's actually purposefully NOT
77+
// using try/catch optimizations.
78+
protected _next(value: T) {
79+
let result: any;
80+
try {
81+
result = this.project.call(this.thisArg, value, this.count++);
82+
} catch (err) {
83+
this.destination.error(err);
84+
return;
85+
}
86+
this.destination.next(result);
87+
}
88+
}

0 commit comments

Comments
 (0)