简体   繁体   English

JS中的反应流如何工作?

[英]How do reactive streams in JS work?

I'm novice in reactive streams and now trying to understand them. 我是反应流中的新手,现在正试图理解它们。 The idea looks pretty clear and simple, but on practice I can't understand what's really going on there. 这个想法看起来非常简单明了,但在实践中我无法理解那里到底发生了什么。

For now I'm playing with most.js , trying to implement a simple dispatcher. 现在我正在玩most.js ,尝试实现一个简单的调度程序。 The scan method seems to be exactly what I need for this. 扫描方法似乎正是我需要的。

My code: 我的代码:

var dispatch;
// expose method for pushing events to stream:
var events = require("most").create(add => dispatch = add); 
// initialize stream, so callback in `create` above is actually called
events.drain();

events.observe(v => console.log("new event", v));

dispatch(1);

var scaner = events.scan(
  (state, patch) => {
    console.log("scaner", patch);
    // update state here
    return state;
  },
  { foo: 0 }
);

scaner.observe(v => console.log("scaner state", v));

dispatch(2);

As I understand, the first observer should be called twice (once per event), and scaner callback and second observer – once each (because they were added after triggering first event). 据我所知,第一个观察者应该被调用两次(每个事件一次),scaner回调和第二个观察者 - 每次一次(因为它们是在触发第一个事件后添加的)。

On practice, however, console shows this: 然而,在练习中,控制台显示:

new event 1
new event 2
scaner state { foo: 0 }

Scaner is never called, no matter how much events I push in stream. 无论我推进多少事件,都不会调用Scaner。

But if I remove first dispatch call (before creating scaner), everything works just as I expected. 但是如果我删除第一个dispatch调用(在创建scaner之前),一切都按照我的预期工作。

Why is this? 为什么是这样? I'm reading docs, reading articles, but so far didn't found anything even similar to this problem. 我正在阅读文档,阅读文章,但到目前为止还没有发现任何与此问题相似的内容。 Where am I wrong in my suggestions? 我的建议在哪里错了?

Most probably, you have studied examples like this from the API : 最有可能的是,您已经从API学习了这样的示例:

most.from(['a', 'b', 'c', 'd'])
    .scan(function(string, letter) {
         return string + letter;
        }, '')
    .forEach(console.log.bind(console));

They are suggesting a step-by-step execution like this: 他们建议像这样一步一步的执行:

  1. Get an array ['a', 'b', 'c', 'd'] and feed its values into the stream. 获取数组['a', 'b', 'c', 'd']并将其值提供给流。
  2. The values fed are transformed by scan() . 馈送的值由scan()转换。
  3. ... and consumed by forEach() . ...并由forEach()

But this is not entirely true. 但这并非完全正确。 This is why your code doesn't work. 这就是您的代码不起作用的原因。

Here in the most.js source code, you see at line 1340 ff.: most.js源代码中,您可以在第1340行看到ff。:

exports.from = from;

function from(a) {
    if(Array.isArray(a) || isArrayLike(a)) {
        return fromArray(a);
    }  
...

So from() is forwarding to some fromArray() . 所以from()转发到一些fromArray() Then, fromArray() (below in the code) is creating a new Stream : 然后, fromArray() (在代码下面)创建一个新的Stream

...
function fromArray (a) {
    return new Stream(new ArraySource(a));
}
...

If you follow through, you will come from Stream to sink.event(0, array[i]); 如果你继续,你将来自Stream to sink.event(0, array[i]); , having 0 for timeout millis. ,超时毫秒为0。 There is no setTimeout in the code, but if you search the code further for .event = function , you will find a lot of additional code that uncovers more. 代码中没有setTimeout ,但是如果你进一步搜索.event = function的代码,你会发现很多额外的代码可以发现更多。 Specially, around line 4692 there is the Scheduler with delay() and timestamps. 特别是,在4692行附近有Schedulerdelay()和timestamps。

To sum it up: the array in the example above is fed into the stream asynchronously, after some time, even if the time seems to be 0 millis. 总结一下:上面例子中的数组在一段时间后异步进入流中,即使时间似乎是0毫秒。

Which means you have to assume that somehow, the stream is first built, and then used. 这意味着您必须假设以某种方式首先构建流,然后使用。 Even if the program code doesn't look that way. 即使程序代码看起来不那样。 But hey, isn't it always the target to hide complexity :-) ? 但是,嘿,是不是总是隐藏复杂性的目标:-)?

Now you can check this with your own code. 现在您可以使用自己的代码进行检查。 Here is a fiddle based on your snippet: 这是一个基于你的片段的小提琴:

https://jsfiddle.net/aak18y0m/1/ https://jsfiddle.net/aak18y0m/1/

Look at your dispatch() calls in the fiddle. 在小提琴中查看你的dispatch()调用。 I have wrapped them with setTimeout() : 我用setTimeout()包装它们:

setTimeout( function() { dispatch( 1 /* or 2 */); }, 0);

By doing so, I force them also to be asynchronous calls, like the array values in the example actually are. 通过这样做,我强制它们也是异步调用,就像示例中的数组值实际上一样。

In order to run the fiddle, you need to open the browser debugger (to see the console) and then press the run button above. 要运行小提琴,您需要打开浏览器调试器(查看控制台),然后按上面的运行按钮。 The console output shows that your scanner is now called three times: 控制台输出显示您的扫描仪现在被调用三次:

doc ready
(index):61 Most loaded: [object Object]
(index):82 scanner state Object {foo: 0}
(index):75 scanner! 1
(index):82 scanner state Object {foo: 0}
(index):75 scanner! 2
(index):82 scanner state Object {foo: 0}

First for drain() , then for each event. 首先是drain() ,然后是每个事件。

You can also reach a valid result (but it's not the same behind scenes) if you use dispatch() synchronously, having them added at the end, after JavaScript was able to build the whole stream. 如果你在JavaScript能够构建整个流之后同步使用dispatch() ,最后添加它们,你也可以得到一个有效的结果(但在幕后不一样)。 Just uncomment the lines after // Alternative solution , run again and watch the result. 只需在// Alternative solution之后取消注释行,再次运行并观察结果。

Well, my question appears to be not so general as it sounds. 好吧,我的问题似乎并不像听起来那么笼统。 It's just a lib-specific one. 它只是一个特定于lib的。

First – approach from topic is not valid for most.js . 首先 - 来自主题的方法对于most.js They argue to 'take a declarative, rather than imperative, approach'. 他们主张“采取陈述性而非强制性的方法”。

Second – I tried Kefir.js lib, and with it code from topic works perfect. 第二 - 我尝试了Kefir.js lib,并使用它来完成主题的代码。 Just works. 只是工作。 Even more, the same approach which is not supported in most.js , is explicitly recommended for Kefir.js . 更重要的是,对于most.js明确推荐使用Kefir.js不支持的相同方法。

So, the problem is in a particular lib implementation, not in my head. 所以,问题出在特定的lib实现中,而不是我的脑海中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM