起因
想到這個庫的原因,是看了callbag庫想到的,callbag庫的原理大家可以自己找資料瞭解,我就不多贅述,我只談談我的理解。callbag的設計思路是把消費者和生產者合併成一個,通過互相傳遞一個回調函數實現通訊。看過部分操作符實現原理的同學肯定覺得邏輯十分難解,因爲過多的回調使得你的腦回路不夠用了。我用了一些庫函數後,我意識到,其實不需要如此複雜的設計,爲什麼呢?請看下文
大同小異的callbag
callbag裏面有很多代碼是重複書寫的,原因很簡單,功能是確定的,如訂閱功能,這是必不可少的操作,下面我來比較一下我的庫的實現和callbag的實現。
對比實現生產者interval
先上callbag的源碼
const interval = period => (start, sink) => {
if (start !== 0) return;
let i = 0;
const id = setInterval(() => {
sink(1, i++);
}, period);
sink(0, t => {
if (t === 2) clearInterval(id);
});
};
export default interval;
說明一下
if(start!=0)return
這句話在callbag實現庫裏面隨處可見,我就是因爲這句話引起的思考,爲什麼每次都要重複寫呢? 當然是因爲這是一個生產者,只發送數據,不會去接受數據。
sink(0, t => {
if (t === 2) clearInterval(id);
});
上面這段代碼其實是實現了一個取消訂閱功能,實現方法是向傳來的回調函數再傳回一個回調函數,估計讀者腦子要燒糊了。
上面這個interval可觀察對象的原型可以代表大多數的callbag的案例,那麼有沒有辦法用更爲簡潔的方式實現呢?
ShowTime
exports.interval = period => n => {
let i = 0;
const id = setInterval(() => n(i++), period)
return () => clearInterval(id)
}
什麼,只有這麼幾行代碼嗎?,沒錯,這就是我認爲實現代碼最小的庫了,不服來戰。此代碼不僅小,性能好,還通俗易懂。當然我還是得稍微解釋一下要使得interval(1000)
成爲一個地道的生產者,必須要實現可以訂閱,可以取消訂閱,以及可以得到生產者發出的數據(有些還需要得到complete和error事件,interval不會complete也不會error)
interval(1000)
將得到一個函數n=>……
,這個函數接受一個next函數用於發送數據- 調用
interval(1000)
這個高階函數等同於“訂閱”,此處是重點(代替了callbag中發送type爲0的行爲) - 返回的是一個dispose函數,即用於“取消訂閱”的功能(代替了callbag中傳回一個回調並在裏面接受type爲2的行爲)
- 函數中調用了傳入的next函數n,即發送出去了數據
當然interval不會獨立工作,我們需要更多的操作符和觀察者使得庫來運作。
對比操作符filter
下面是callbag的實現
const filter = condition => source => (start, sink) => {
if (start !== 0) return;
let talkback;
source(0, (t, d) => {
if (t === 0) {
talkback = d;
sink(t, d);
} else if (t === 1) {
if (condition(d)) sink(t, d);
else talkback(1);
}
else sink(t, d);
});
};
module.exports = filter;
依然出現了
if(start!=0)return
沒錯,因爲filter只用於被訂閱,本身作爲數據響應者,有人說不對,filter需要對上一級的源做響應,沒錯,所以需要訂閱上一級的源,但傳入的不是自身,而是另一個回調函數來響應,否則就會有問題。核心代碼就一句,卻需要一大堆代碼來維持正常運行,我看不下去了。
ShowTime
exports.filter = f => source => (n, c) => source(d => f(d) && n(d), c)
What?就一行代碼?你沒看錯,你沒看錯,你沒看錯!
我來解釋一下,這一行代碼。filter是一個操作符,filter(d=>d>1)
代表我只接受大於1的數據,這個將返回一個source=>……
的函數,這個函數接受一個source作爲上一級數據源,可以是上文的interval(1000)
這樣的生產者,也可以是其他操作符。所以
const obserable = filter(d => d > 1)(interval(1000))
你將得到一個(n,c)=>……
的函數,這個就是可觀察者,你可以傳入next函數n,和complete函數c來進行“訂閱”了
const disposable = obserable(d => console.log('得到',d),err => console.log('完成'))//err代表有錯誤,這裏先不處理
你訂閱過後會得到一個函數disposable,用於“取消訂閱”
disposable()//取消訂閱
這個filter代表了最小庫的精髓:disposable可以從箭頭函數一路返回,在filter中是隱含的,無需顯示實現而代表complete的c函數也是直接透傳,無需更改。唯獨需要操作的就是next函數,需要向source傳一個新的next函數。當滿足條件時就向下一級的next函數發送數據,否則啥也不幹。
(未完待續)