The basis of the whole RxJS is Observable. As long as you understand an Observable, you can learn half of RxJS. The rest is just practice and familiarity with some methods. But what exactly is an Observable?

Mastering RxJS in 30 Days (03) %EF%BC%9A%20Functional%20Programming%20%E9%80%9A%E7%94%A8%E5%87%BD%E6%95%B0.md)

Observable to understand Observable, we need to talk about two Design patterns: Iterator Pattern and Observer Pattern. This article will give you a quick understanding of these two design patterns and explain the relationship between these two patterns and Observables.

The Observer Pattern is very interesting.

In fact, Observer Pattern is very common, in many API design have used the Observer Pattern instance, the simplest example is the DOM event listener, code as follows

function clickHandler(event) {
    console.log('user click! ');
}

document.body.addEventListener('click', clickHandler)Copy the code

In the above code, we set up a clickHandler function and use the addEventListener for a DOM event (body in the example) to listen for a click event. Each time the user clicks on the body mouse, a clickHandler is executed and an event is brought in! This is the observer mode. We can register a listener for something and automatically execute our registered listener when the event occurs.

In fact, the concept of the Observer is so simple, but I hope to guide you through the code, how to example such Pattern!

First we need a construct that can be listened to when new instances are created.

Here we first use ES5 notation, and we will attach ES6 notation

function Producer() {

    // This if is used to prevent users from accidentally calling Producer as a function
    if(! (this instanceof Producer)) {
      throw new Error('Please use new Producer()! ');
      // Invoke ES6 action: throw new Error('Class constructor Producer cannot be invoked without 'new'')
    }

    this.listeners = [];
}

// Add a listening method
Producer.prototype.addListener = function(listener) {
    if(typeof listener === 'function') {
        this.listeners.push(listener)
    } else {
        throw new Error('Listener must be function')}}// Remove the listening method
Producer.prototype.removeListener = function(listener) {
    this.listeners.splice(this.listeners.indexOf(listener), 1)}// The notification method
Producer.prototype.notify = function(message) {
    this.listeners.forEach(listener= >{ listener(message); })}Copy the code

Here I use concepts like this and prototype. If you don’t understand, you can watch one of my videos dedicated to explaining these concepts!

Attach the ES6 version of the code, the behavior of the above code is basically the same

class Producer {
    constructor() {
        this.listeners = [];
    }
    addListener(listener) {
        if(typeof listener === 'function') {
            this.listeners.push(listener)
        } else {
            throw new Error('Listener must be function')
        }
    }
    removeListener(listener) {
        this.listeners.splice(this.listeners.indexOf(listener), 1)
    }
    notify(message) {
        this.listeners.forEach(listener= >{ listener(message); }}})Copy the code

With the above code, we are ready to create the event instance

var egghead = new Producer(); 
// Create a new Producer instance named egghead

function listener1(message) {
    console.log(message + 'from listener1');
}

function listener2(message) {
    console.log(message + 'from listener2');
}

egghead.addListener(listener1); // Register listener
egghead.addListener(listener2);

egghead.notify('A new course!! ') // When something is done, executeCopy the code

When we get to this point, we will print:

a new course!! from listener1
a new course!! from listener2Copy the code

Each time egghead. Notify is executed, Listener1 and Listener2 are notified, and these listeners can be added or removed!

Although our example is simple, it well illustrates the decoupling of Observer Pattern in the interaction of events and listeners.

Iterator Pattern

Iterator is an event that, like a pointer, points to a data structure and produces a sequence of all the elements in the data structure.

Let’s look at how native JS builds iterator

var arr = [1.2.3];

var iterator = arr[Symbol.iterator]();

iterator.next();
// { value: 1, done: false }
iterator.next();
// { value: 2, done: false }
iterator.next();
// { value: 3, done: false }
iterator.next();
// { value: undefined, done: true }Copy the code

JavaScript doesn’t have a native Iterator until ES6

In ECMAScript, Iterator was originally intended to adopt the Python-like Iterator specification, which means that Iterator will throw an error when executing next after it has no elements. After some discussion, however, we decided to do something more functional, instead executing next after the last element is returned {done: true, value: undefined}

JavaScript Iterator has only one next method, which returns only two results:

  1. Before the last element:{ done: false, value: elem }
  2. After the last element:{ done: true, value: undefined }

Of course we can instantiate the simple Iterator Pattern ourselves

function IteratorFromArray(arr) {
    if(! (this instanceof IteratorFromArray)) {
        throw new Error('Use new IteratorFromArray()! ');
    }
    this._array = arr;
    this._cursor = 0;    
}

IteratorFromArray.prototype.next = function() {
    return this._cursor < this._array.length ?
        { value: this._array[this._cursor++], done: false}, {done: true };
}Copy the code

Attach the ES6 version of the code, the same behavior

class IteratorFromArray {
    constructor(arr) {
        this._array = arr;
        this._cursor = 0;
    }

    next() {
        return this._cursor < this._array.length ?
        { value: this._array[this._cursor++], done: false}, {done: true}; }}Copy the code

Although the Iterator Pattern is very simple, it brings two advantages at the same time. First, it can be used for Lazy evaluation, which allows us to process large data structures with it. Second, because iterator is itself a sequence, it can instantiate all the operations on arrays like map, filter… Etc.!

Let’s try this with the last code example map

class IteratorFromArray {
    constructor(arr) {
        this._array = arr;
        this._cursor = 0;
    }

    next() {
        return this._cursor < this._array.length ?
        { value: this._array[this._cursor++], done: false}, {done: true };
    }

    map(callback) {
        const iterator = new IteratorFromArray(this._array);
        return {
            next: (a)= > {
                const { done, value } = iterator.next();
                return {
                    done: done,
                    value: done ? undefined : callback(value)
                }
            }
        }
    }
}

var iterator = new IteratorFromArray([1.2.3]);
var newIterator = iterator.map(value= > value + 3);

newIterator.next();
// { value: 4, done: false }
newIterator.next();
// { value: 5, done: false }
newIterator.next();
// { value: 6, done: false }Copy the code

Lazy evaluation

Delayed operation, or call-by-need, is an evaluation strategy in which we delay the operation of an expression until its value is actually needed.

Let’s use a generator instance iterator as an example

    function* getNumbers(words) {
        for (let word of words) {
            if (/ ^ [0-9] + $/.test(word)) {
                yield parseInt(word, 10); }}}const iterator = getNumbers('30 Days to Master RxJS (04)');

    iterator.next();
    // { value: 3, done: false }
    iterator.next();
    // { value: 0, done: false }
    iterator.next();
    // { value: 0, done: false }
    iterator.next();
    // { value: 4, done: false }
    iterator.next();
    // { value: undefined, done: true }Copy the code

Here we write a function to grab the number in the string. In this function we use for… Of is used to get each character and determine if it is a value with the regular representation. If it is true, it is converted to a value and returned. When we drop a string into getNumbers, we do not immediately calculate all the numbers in the string. We have to wait until we execute next() to do the actual calculation. This is called the evaluation strategy.

Observable

After learning about the Observer and Iterator, I wonder if you have noticed that there is a common feature between the Observer and Iterator, that is, they are both progressive data acquisition. The only difference is that the Observer is a Producer who pushes data while the Iterator is a Consumer who pulls data!

push & pull

An Observable is actually the combination of these two patterns. An Observable has the feature of data push by producers, and can image sequences and process data in sequence (map, filter…). !

More simply, an Observable is like a sequence in which elements are pushed over time.

Note that this is a combination of ideas. There are still differences between observables and observers in terms of instances, which we’ll talk about in the next article.

Today’s summary

The two patterns of Iterator and Observer are both incremental acquisition elements. The difference lies in that the Observer relies on the producer to push the information, while the Iterator requires the consumer to request the information. An Observable is a combination of these two ideas!

Today’s concept requires a lot of thinking, I hope readers can take a little more patience to think about it, if you have any questions please leave a comment to me below.