30 天精通 RxJS(27):简易实作 Observable(二)

转载
前一篇文章我们已经完成了基本的 observable 以及 Observer 的简易实作,这篇文章我们会接续上一篇的内容实作简易的 Observable 类别,以及一个 creation operator 和一个 transform operator。

建立简易 Observable 类别

这是我们上一篇文章写的建立 observable 物件的函式

function create(subscribe) {
    const observable = {
        subscribe: function() {
            const realObserver = new Observer(...arguments);
            subscribe(realObserver);
            return realObserver;
        }       
    };
    return observable;
}

JSBin

从这个函式可以看出来,回传的 observable 物件至少会有 subscribe 方法,所以最简单的 Observable 类别大概会长像下面这样

class Observable {
  subscribe() {
    // ...做某些事
  }
}

另外 create 的函式在执行时会传入一个 subscribe 的 function,这个 function 会决定 observable 的行为

var observable = create(function(observer) {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();
  observer.next('not work');
})

把上面这一段改成下面这样

var observable = new Observable(function(observer) {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();
  observer.next('not work');
})

所以我们的 Observable 的建构式应该会接收一个 subscribe function

class Observable {
  constructor(subscribe) {
    if(subscribe) {
      this._subscribe = subscribe; // 把 subscribe 存到属性中
    }
  }
  subscribe() {
    // ...做某些事
  }
}

接著我们就能完成 subscribe 要做的事情了

class Observable {
  constructor(subscribe) {
    if(subscribe) {
      this._subscribe = subscribe; // 把 subscribe 存到 _subscribe 属性中
    }
  }
  subscribe() {
    const observer = new Observer(...arguments);
    this._subscribe(observer); // 就是执行一个 function 对吧
    return observer;
  }
}

到这裡我们就成功的把 create 的函式改成 Observable 的类别了,我们可以直接来使用看看

class Observable {
  constructor(subscribe) {
    if(subscribe) {
      this._subscribe = subscribe; // 把 subscribe 存到属性中
    }
  }
  subscribe() {
    const observer = new Observer(...arguments);
    this._subscribe(observer);
    return observer;
  }
}

var observable = new Observable(function(observer) {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();
  observer.next('not work');
})

var observer = {
  next: function(value) {
    console.log(value)
  },
  complete: function() {
      console.log('complete!')
  }
}

observable.subscribe(observer);

JSBin

当然我们可以仿 RxJS 在静态方法中加入 create,如下

class Observable {
  constructor(subscribe) {
    if(subscribe) {
      this._subscribe = subscribe; // 把 subscribe 存到属性中
    }
  }
  subscribe() {
    const observer = new Observer(...arguments);
    this._subscribe(observer);
    return observer;
  }
}

Observable.create = function(subscribe) {
    return new Observable(subscribe);
}

这样一来我们就可以用 Observable.create 建立 observable 物件实例。

var observable = Observable.create(function(observer) {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();
  observer.next('not work');
});

JSBin

建立 creation operator - fromArray

当我们有 Observable 类别后要建立 creation operator 就不难了,这裡我们建立一个 fromArray 的方法,可以接收 array 来建立 observable,算是 Rx 的 Observable.from 的简化版本,记得 creation operators 都属于 static 方法

class Observable {
  constructor(subscribe) {
    if(subscribe) {
      this._subscribe = subscribe; // 把 subscribe 存到属性中
    }
  }
  subscribe() {
    const observer = new Observer(...arguments);
    this._subscribe(observer);
    return observer;
  }
}

// 建立静态方法 
Observable.fromArray = function(array) {
    if(!Array.isArray(array)) {
        // 如果传入的参数不是阵列,则抛出例外
        throw new Error('params need to be an array');
    }
    return new Observable(function(observer) {
        try{
            // 遍历每个元素并送出
            array.forEach(value => observer.next(value))
            observer.complete()
        } catch(err) {
            observer.error(err)
        }
    });
}

var observable = Observable.fromArray([1,2,3,4,5]);

JSBin

上面的程式码我们只是简单的用 new Observable 就可以轻鬆地实现我们要的功能,之后就可以用 fromArray 来建立 observable 物件。

相信读者到这之前应该都不会有太大的问题,接下来这个部份就困难的多,请读者们一定要搞懂前面的各个实作再接著往下看。

建立 transform operator - map

相信很多人在实作 Observable 都是卡在这个阶段,因为 operators 都是回传一个新的 observable 这中间有很多细节需要注意,并且有些小技巧才能比较好的实现,在开始实作之前先让我们釐清几个重点

  • operators(transform, filter, conditional...) 都是回传一个新个 observable
  • 大部分的 operator 其实就是在原本 observer 外包裹一层物件,让执行 next 方法前先把元素做一次处理
  • operator 回传的 observable 订阅时,还是需要执行原本的 observable(资料源),也就说我们要想办法保留原本的 observable

让我们一步一步来,首先 operators 执行完会回传一个新的 observable,这个 observable 在订阅时会先去执行 operator 的行为再发送元素,所以 observable 的订阅方法就不能像现在这样直接把 observer 传给 subscribe 执行

class Observable {
  constructor(subscribe) {
    if(subscribe) {
      this._subscribe = subscribe; // 把 subscribe 存到属性中
    }
  }
  subscribe() {
    const observer = new Observer(...arguments);
    // 先做某个判断是否当前的 observable 是具有 operator 的
    if(??) {
      // 用 operator 的操作
    } else {
      // 如果没有 operator 再直接把 observer 丢给 _subscribe
      this._subscribe(observer);
    }
    return observer;
  }
}

以我们的 Observable 实作为例,这裡最重要的就是 this._subscribe 执行,每当执行时就是开始发送元素。

这裡我们可以想像一下当一个 map 产生的 observable 订阅时,应该先判断出有 map 这个 operator 并且传入原本的资料源以及当前的 observer。也就是说我们的 map 至少有以下这几件事要做

  • 建立新的 observable
  • 保存原本的 observable(资料源),之后订阅时才有办法执行
  • 建立并保存 operator 本身的行为,等到订阅时执行
class Observable {
  constructor(subscribe) {
    // 一些程式码...
  }
  subscribe() {
    // 一些程式码...
  }
  map(callback) {
    const observable = new Observable(); // 建立新的 observable

    observable.source = this; // 保存当前的 observable(资料源)

    observable.operator = {
        call: (observer, source) => { // 执行这个 operator 的行为 }
    }; // 储存当前 operator 行为,并作为是否有 operator 的依据,

    return observable; // 返回这个新的 observable
  }
}

上面这三个步骤都是必要的,特别是用到了 observable.source = this 这个小技巧,来保存原本的 observable。但这裡我们还有一个地方没完成就是 operator 要做的事,这个部分我们等一下再补,先把 subscribe 写完

class Observable {
  constructor(subscribe) {
    // 一些程式码...
  }
  subscribe() {
    const observer = new Observer(...arguments);
    // 先用 this.operator 判断当前的 observable 是否具有 operator 
    if(this.operator) {
      this.operator.call(observer, this.source)
    } else {
      // 如果没有 operator 再直接把 observer 丢给 _subscribe
      this._subscribe(observer);
    }
    return observer;
  }
  map(callback) {
    const observable = new Observable(); // 建立新的 observable

    observable.source = this; // 保存当前的 observable(资料源)

    observable.operator = {
        call: (observer, source) => { // 执行这个 operator 的行为 }
    }; // 储存当前 operator 行为,并作为是否有 operator 的依据,

    return observable; // 返回这个新的 observable
  }
}

记得这裡补的 subscribe 行为,已经是 map 回传新 observable 的行为,不是原本的 observable 了。

到这裡我们就几乎要完成了,接著只要实作 map 这个 operator 的行为就可以萝!记得我们在前面讲的 operator 其实就是在原本的 observer 做一层包裹,让 next 执行前先对元素做处理,所以我们改写一下 Observer 并建立一个 MapObserver 来做这件事

class Observer {
  constructor(destinationOrNext, error, complete) {
    switch (arguments.length) {
      case 0:
        this.destination = this.safeObserver(emptyObserver);
        break;
      case 1:
        if (!destinationOrNext) {
          this.destination = this.safeObserver(emptyObserver);
          break;
        }
        // 多一个判断,是否传入的 destinationOrNext 原本就是 Observer 的实例,如果是就不用在用执行 `this.safeObserver`
        if(destinationOrNext instanceof Observer){
          this.destination = destinationOrNext;
          break;
        }
        if (typeof destinationOrNext === 'object') {
          this.destination = this.safeObserver(destinationOrNext);
          break;
        }
      default:
        this.destination = this.safeObserver(destinationOrNext, error, complete);
        break;
    }
  }

  // ...下面都一样
}

class MapObserver extends Observer {
  constructor(observer, callback) {
    // 这裡会传入原本的 observer 跟 map 的 callback
    super(observer); // 因为有继承所以要先执行一次父层的建构式
    this.callback = callback; // 保存 callback
    this.next = this.next.bind(this); // 确保 next 的 this
  }
  next(value) {
    try {
      this.destination.next(this.callback(value)); 
      // this.destination 是父层 Observer 保存的 observer 物件
      // 这裡 this.callback(value) 就是 map 的操作
    } catch (err) {
      this.destination.error(err);
      return;
    }
  }
}

上面这段程式码就可以让我们包裹 observer 物件,利用物件的继承覆写原本的 next 方法。

最后我们就只要补完 map 方法就可以了

class Observable {
  constructor(subscribe) {
    // 一些程式码...
  }
  subscribe() {
    // 一些程式码...
  }
  map(callback) {
    const observable = new Observable(); 
    observable.source = this;
    observable.operator = {
      call: (observer, source) => { 
        // 执行这个 operator 的行为
        const newObserver = new MapObserver(observer, callback);
        // 建立包裹后的 observer
        // 订阅原本的资料源,并回传
        return source.subscribe(newObserver);
      }
    };    
    return observable; 
  }
}

这裡做的事情就简单很多,我们只要建立包裹过的 observer,并用这个包裹后的 observer 订阅原本的 source。(记得这个 function 是在 subscribe 时执行的)

这裡有完整的程式码,可以让大家参考。

另外这裡有抽出 lift 方法的实作,其实跟我们现在的版本很接近了,只是把建立新的 observable 封装到 lift 而已。

今日小结

今天这篇文章介绍了要如何简易的实作 Observable,虽然说是简易版本但实际上已经非常非常接近 RxJS 官方的实作了,希望读者花点耐心一步一步跟著程式码做,做出来后再慢慢吸收。

不知道今天读者们有没有收穫呢? 如果有任何问题,欢迎在下方留言给我,谢谢!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容