30 天精通 RxJS(26):簡易實作 Observable(一)

轉(zhuǎn)載
因為實在太多讀者在問要如何實作 Observable洽胶,所以特別調(diào)整了本系列文章最后幾篇的內(nèi)容苇羡,空出一天的位置來寫如何簡易實作 Observable。

為什麼是簡易實作而不完整實作呢羡滑? 當(dāng)然這個系列的文章是希望讀者能學(xué)會如何使用 RxJS聪姿,而 實作 Observable 其實只是幫助我們理解 Observable 的運作方式,所以這篇文章會盡可能地簡單饶号,一來讓讀者容易理解及吸收铁追,二來有興趣的讀者可以再沿著這篇文章的內(nèi)容去完整的實作。

重點觀念

Observable 跟 Observer Pattern 是不同的茫船,Observable 內(nèi)部并沒有管理一份訂閱清單琅束,訂閱 Observable 就像是執(zhí)行一個 function 一樣

所以實作過程的重點

  • 訂閱就是執(zhí)行一個 funciton
  • 訂閱接收的物件具備 next, error, complete 三個方法
  • 訂閱會返回一個可退訂(unsubscribe)的物件

基本 observable 實作

先用最簡單的 function 來建立 observable 物件

function create(subscriber) {
    var observable = {
        subscribe: function(observer) {
            subscriber(observer)
        }       
    };
    return observable;
}

上面這段程式碼就可以做最簡單的訂閱算谈,像下面這樣

function create(subscriber) {
    var observable = {
        subscribe: function(observer) {
            subscriber(observer)
        }       
    };
    return observable;
}

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

var observer = {
  next: function(value) {
    console.log(value)
  }
}

observable.subscribe(observer)
// 1
// 2
// 3

JSBin

這時我們已經(jīng)有最簡單的功能了涩禀,但這裡有一個大問題,就是 observable 在結(jié)束(complete)就不應(yīng)該再發(fā)送元素

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

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

observable.subscribe(observer)
// 1
// 2
// 3
// "complete!"
// "still work"

JSBin

從上面的程式碼可以看到 complete 之后還是能送元素出來然眼,另外還有一個問題就是 observer艾船,如果是不完整的就會出錯,這也不是我們希望看到的。

var observable = create(function(observer) {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete(); // error: complete is not a function 
})

var observer = {
  next: function(value) {
    console.log(value)
  }
}

observable.subscribe(observer)
// 1
// 2
// 3
// "complete!"
// "still work"

JSBin

上面這段程式碼可以看出來屿岂,當(dāng)使用者 observer 物件沒有 complete 方法時践宴,就會報錯。
我們應(yīng)該修正這兩個問題爷怀!

實作簡易 Observer

要修正這兩個問題其實并不難阻肩,我們只要實作一個 Observer 的類別,每次使用者傳入的 observer 都會利用這個類別轉(zhuǎn)乘我們想要 Observer 物件运授。

首先訂閱時有可能傳入一個 observer 物件烤惊,或是一到三個 function(next, error, complete),所以我們要建立一個類別可以接受各種可能的參數(shù)

class Observer {
  constructor(destinationOrNext, error, complete) {
    switch (arguments.length) {
      case 0:
        // 空的 observer
      case 1:
        if (!destinationOrNext) {
          // 空的 observer
        }
        if (typeof destinationOrNext === 'object') {
          // 傳入了 observer 物件
        }
      default:
        // 如果上面都不是吁朦,代表應(yīng)該是傳入了一到三個 function
        break;
    }
  }
}

寫一個方法(safeObserver)來回傳正常的 observer

class Observer {
  constructor(destinationOrNext, error, complete) {
    // ... 一些程式碼
  }
  safeObserver(observerOrNext, error, complete) {
    let next;

    if (typeof (observerOrNext) === 'function') {
      // observerOrNext 是 next function
      next = observerOrNext;
    } else if (observerOrNext) {
      // observerOrNext 是 observer 物件
      next = observerOrNext.next || () => {};
      error = observerOrNext.error || function(err) { 
        throw err 
      };
      complete = observerOrNext.complete || () => {};
    }
    // 最后回傳我們預(yù)期的 observer 物件
    return {
      next: next,
      error: error,
      complete: complete
    };
  }
}

再把 constructor 完成

// 預(yù)設(shè)空的 observer 
const emptyObserver = {
  next: () => {},
  error: (err) => { throw err; },
  complete: () => {}
}

class Observer {
  constructor(destinationOrNext, error, complete) {
    switch (arguments.length) {
      case 0:
        // 空的 observer
        this.destination = this.safeObserver(emptyObserver);
        break;
      case 1:
        if (!destinationOrNext) {
          // 空的 observer
          this.destination = this.safeObserver(emptyObserver);
          break;
        }
        if (typeof destinationOrNext === 'object') {
          // 傳入了 observer 物件
          this.destination = this.safeObserver(destinationOrNext);
          break;
        }
      default:
        // 如果上面都不是柒室,代表應(yīng)該是傳入了一到三個 function
        this.destination = this.safeObserver(destinationOrNext, error, complete);
        break;
    }
  }
  safeObserver(observerOrNext, error, complete) {
    // ... 一些程式碼
  }
}

JSBin

這裡我們把真正的 observer 塞到 this.destination,接著完成 observer 的方法逗宜。

Observer 的三個主要的方法(next, error, complete)都應(yīng)該結(jié)束或退訂后不能再被執(zhí)行雄右,所以我們在物件內(nèi)部偷塞一個 boolean 值來作為是否曾經(jīng)結(jié)束的依據(jù)。

class Observer {
  constructor(destinationOrNext, error, complete) {
    // ... 一些程式碼
  }
  safeObserver(observerOrNext, error, complete) {
    // ... 一些程式碼
  }
  unsubscribe() {
    this.isStopped = true; // 偷塞一個屬性 isStopped
  }
}

接著要實作三個主要的方法就很簡單了锦溪,只要先判斷 isStopped 在使用 this.destination 物件來傳送值就可以了

class Observer {
  constructor(destinationOrNext, error, complete) {
    // ... 一些程式碼
  }
  safeObserver(observerOrNext, error, complete) {
    // ... 一些程式碼
  }

  next(value) {
    if (!this.isStopped && this.next) {
      // 先判斷是否停止過
      try {
        this.destination.next(value); // 傳送值
      } catch (err) {
        this.unsubscribe();
        throw err;
      }
    }
  }

  error(err) {
    if (!this.isStopped && this.error) {
      // 先判斷是否停止過
      try {
        this.destination.error(err); // 傳送錯誤
      } catch (anotherError) {
        this.unsubscribe();
        throw anotherError;
      }
      this.unsubscribe();
    }
  }

  complete() {
    if (!this.isStopped && this.complete) {
      // 先判斷是否停止過
      try {
        this.destination.complete(); // 發(fā)送停止訊息
      } catch (err) {
        this.unsubscribe();
        throw err;
      }
      this.unsubscribe(); // 發(fā)送停止訊息后退訂
    }
  }

  unsubscribe() {
    this.isStopped = true;
  }
}

JSBin

到這裡我們就完成基本的 Observer 實作了不脯,接著讓我們拿到基本版的 observable 中使用吧府怯。

function create(subscriber) {
    const observable = {
        subscribe: function(observerOrNext, error, complete) {
            const realObserver = new Observer(observerOrNext, error, complete)
            subscriber(realObserver);
            return realObserver;
        }       
    };
    return observable;
}

var observable = create(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);
// 1
// 2
// 3
// complete!

JSBin

到這裡我們就完成最基本的 observable 了刻诊,至少基本的行為都跟我們期望的一致,我知道讀者們?nèi)匀徊粫胚^我牺丙,你們會希望做出一個 Observable 型別以及至少一個 operator 對吧则涯? 不用擔(dān)心,我們下一篇就會講解如何建立一個 Observable 型別和 operator 的方法冲簿!

今日小結(jié)

今天我們複習(xí)了 Observable 的重要概念粟判,并用這些重要的概念實作出了基本的 observable 以及 Observer 的類別。

不知道今天讀者們有沒有收穫呢峦剔? 如果有任何問題档礁,歡迎在下方留言給我,謝謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吝沫,一起剝皮案震驚了整個濱河市呻澜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惨险,老刑警劉巖羹幸,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辫愉,居然都是意外死亡栅受,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屏镊,“玉大人依疼,你說我怎么就攤上這事《妫” “怎么了涛贯?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蔚出。 經(jīng)常有香客問我弟翘,道長,這世上最難降的妖魔是什么骄酗? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任稀余,我火速辦了婚禮,結(jié)果婚禮上趋翻,老公的妹妹穿的比我還像新娘睛琳。我一直安慰自己,他們只是感情好踏烙,可當(dāng)我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布师骗。 她就那樣靜靜地躺著,像睡著了一般讨惩。 火紅的嫁衣襯著肌膚如雪辟癌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天荐捻,我揣著相機與錄音黍少,去河邊找鬼。 笑死处面,一個胖子當(dāng)著我的面吹牛厂置,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播魂角,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼昵济,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了野揪?” 一聲冷哼從身側(cè)響起访忿,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎囱挑,沒想到半個月后醉顽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡平挑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年游添,在試婚紗的時候發(fā)現(xiàn)自己被綠了系草。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡唆涝,死狀恐怖找都,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情廊酣,我是刑警寧澤能耻,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站亡驰,受9級特大地震影響晓猛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凡辱,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一戒职、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧透乾,春花似錦洪燥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汉操,卻和暖如春再来,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背客情。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工其弊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人膀斋。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像痹雅,于是被迫代替她去往敵國和親仰担。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,870評論 2 361