起因
想到这个库的原因,是看了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函数发送数据,否则啥也不干。
(未完待续)