25

I'm learning RxJS by reading this tutorial http://reactive-extensions.github.io/learnrx/.

I have a hard time understanding the map method of Observable. The Array version of map is really simple and straightforward. I have no idea about what exactly the map means in case of a Observable(and why does it have a alias named select?!).

Here is what the documentation told me. Might not be helpful for most beginners...

Projects each element of an observable sequence into a new form by incorporating the element's index. This is an alias for the select method.

I don't understand map in the context of event. For example, the code below works exactly what I expected. I thought of this piece of code as : "Listen to the click-event from the event-stream of #btn".

var btnClicks, observable;

btnClicks = Rx.Observable.fromEvent($('#btn'), "click");

observable = btnClicks.subscribe(function(e) {
 console.log(e);
});

But what happens when it becomes to this??

var btn2Clicks, btnClicks, observable;

btnClicks = Rx.Observable.fromEvent($('#btn'), "click");

btn2Clicks = Rx.Observable.fromEvent($('#btn2'), "click");

observable = btnClicks.map(function(e) {
  return btn2Clicks;
}).subscribe(function(e) {
  console.log(e);
});

What I thought is use the map to transform a collection of a click-event to another collection of event-collection. The filter is easy to understand, it just as the word filter means, take the event only I interested, and skip others. But how about the map in the context of event? If it means 'transform a collection to another ones' just as the array version, why it still fires when #btn clicked??

I mean I'v mapped it to another collections, now it's no longer a collection of click-event of #btn but it's a new collection of something... But it still fires when #btn clicked which not make sense for me.

yaquawa
  • 6,690
  • 8
  • 35
  • 48

2 Answers2

41

map works exactly the same for Observables as it does for arrays. You use map to transform a collection of items into a collection of different items. It helps if you think of an Observable as a collection of items (just like an array is also a collection of items), at least from the observer's point of view.

For example, take these 2 methods that you wrote to use with some arrays:

function multiplyByTwo(collection) {
    return collection.map(function (value) {
        return value * 2;
    });
}

function removeZeroes(collection) {
    return collection.filter(function (value) {
        return value !== 0;
    });
}

var a = [1, 2, 3, 4, 0, 5];
var b = multiplyByTwo(a); // a new array [2, 4, 6, 8, 0, 10]
var c = removeZeroes(b); // a new array [2, 4, 6, 8, 10]

You can use these same functions for an observable:

var a = Rx.Observable.of(1, 2, 3, 4, 0, 5);
var b = multiplyByTwo(a); // a new observable [2, 4, 6, 8, 0, 10]
var c = removeZeroes(b); // a new observable [2, 4, 6, 8, 10]

This is possible because RxJs observables implement the array operators like map and filter to have the exact same semantics as they do for arrays. If you know how they work for arrays, then you know how they work for observables.

This trick is the result of the dual nature of observables and enumerables.

If you work through the interactive tutorial you are viewing, it actually walks you through this process. I believe it starts you off by writing map operators for arrays and then in a later tutorial sneaks an observable in as the source.

P.S. It is an alias for select because of its history: Reactive Extensions was first implemented in .NET and later ported to other languages. Rx.NET uses the same operators that are used by .NET's LINQ (since IObservable is the dual of IEnumerable). LINQ's map operator is known as Select (and its filter operator is known as Where). These names come from LINQ's origination. One of the goals when LINQ was built was to make it possible to write database queries in C#. Thus they adopted SQL naming conventions for many of the operators (LINQ SELECT maps directly to SQL SELECT, LINQ WHERE maps to SQL WHERE, etc).

Brandon
  • 38,310
  • 8
  • 82
  • 87
  • Thanks! Now I know why some methods has a alias. But I still can't understand the `map` operation in the context of `event`. I edited the question, hope you can answer me what does map mean when dealing with events. – yaquawa Jan 24 '15 at 06:19
  • 1
    @Brandon but when does the map function iterate? I mean, when you map on an array, you already have all the elements in the array and it iterates log(n) times through each of the elements. When you .map on an Observable, has the observable already received all of the elements (and it iterates through each of them) or do the functions (inside the map block) operate on each element (event) as it arrives? So for Observables, does map act as an iterator (like it does for normal arrays) or is .map more of a decorator for each event (element) to pass through? – Benjamin McFerren Sep 23 '16 at 03:42
  • 1
    @BenjaminMcFerren Observables are push-based ( sources *push* data to subscribers ( through any operators like `map` ) as the data becomes available ). So, yes, in your parlance, the Observable operators are like event decorators. – Brandon Sep 23 '16 at 14:02
  • I'm not quite sure about this. Can you please explain? How can we pass an observable, which has subscribe method and not map, to "multiplyByTwo" function, and expect that it will do some manipulations through that stream O.o – Gh111 Mar 25 '21 at 09:18
  • this answer as written for an older version of RxJs where the Observable prototype had all the methods directly on it. Recent versions only have subscribe() and pipe() and you are supposed to use pipe() to create your functional chain with methods like map(). So the example above won't work anymore. Conceptually it is the same, but now the Observable API has diverged from the Array API a bit. – Brandon Jun 03 '21 at 17:20
4

Map in Rxjs used for projection, which means you can transform the array in to entirely new array. In order to understand how Map works , we can implement our own map function using plain javascript.

Array.prototype.map = function(projectionFunction){
  var results=[];
  this.forEach(function(item) {
    results.push(projectionFunction(item));
  });
  return results;
};

You can see I have written a map function which accepts an anonymous function as a parameter. This will be your function to apply the projection to transform the array. Inside the map function you can see iterating each item in a array , call the project function by passing each item and finally the result of the projection function will push to the results array.

JSON.stringify([1,2,3].map(function(x){return x+1;}))

Output

[2,3,4]
Fouad Boukredine
  • 1,495
  • 14
  • 18
Jameel Moideen
  • 7,542
  • 12
  • 51
  • 79