繁体   English   中英

RxJS:如何在传递下一个有效值之前进行一些清理?

[英]RxJS: How to do some clean-up before the next valid value is passed?

我必须合并流以获取加载图像的URL:一个用于放置事件的流和一个用于文件输入更改的流。 在每个新路径上,我加载此图像并将其绘制到画布上。 此画布将传递到另一个流中。 它看起来像这样:

// prevent browsers default behavior for dropTargetElement
[ 'drop', 'dragover' ].forEach(function(eventName) {
  Rx.Observable.fromEvent(dropTargetElement, eventName).subscribe(function(event) {
    event.preventDefault();
  });
});

// file path stream merged from openFileInputs change and openFileDropTargets drop
Rx.Observable.merge(Rx.Observable.fromEvent(inputElement, 'change').map(function(event) {
  return event.target.value;
}), Rx.Observable.fromEvent(dropTargetElement, 'drop').map(function(event) {
  return event.dataTransfer.files[0].path;
})).map(function(path) {
  var image = new Image();
  image.src = path;

  // note: I return an Observable in this map function
  // is this even good practice? if yes, is mergeAll the best
  // way to get the "load" event?
  return Rx.Observable.fromEvent(image, 'load');
}).mergeAll().map(function(event) {
  return event.path[0];
}).subscribe(function(image) {
  var canvas = document.createElement('canvas');
  var context = canvas.getContext('2d');

  canvas.width = image.width;
  canvas.height = image.height;
  context.drawImage(image, 0, 0);

  canvasState.onNext(canvas);
});

(附带问题:是否“允许”在map返回Observables?)

我的canvasState看起来像这样:

var canvasState = new Rx.BehaviorSubject(undefined);

// draw image
canvasState.filter(function(canvas) {
  return !!canvas;
}).subscribe(function drawImage(canvas) {
  document.body.appendChild(canvas);
});

正如你所看到的,如果我的画布值非常真实,我会将画布附加到主体上。 但是每当有新画布进来时我都想删除旧画布。 实现这一目标的最佳方法是什么? 这样的事情可能吗?

// draw image
canvasState.filter(function(canvas) {
  return !!canvas;
}).beforeNext(function(oldCanvas) {
  // remove old one
}).subscribe(function drawImage(canvas) {
  document.body.appendChild(canvas);
});

嵌套的Observables

是的,让map操作返回Observable是正常的。 这为您提供了一个可观察的可观察流流。 有Rx 3操作来展平嵌套的可观察的一个级别:

  • mergeAll - 在到达时订阅所有内部流。 您通常最终会同时订阅多个流,并且通常您的最终流将具有来自不同内部流混合的结果。 您可以提供maxConcurrent参数来限制并发内部订阅的数量。 当达到限制时,新的内部observable会排队,并且在前一个内部observable完成之前不会被订阅。
  • concatAll - 按顺序一次订阅一个内部可观察流。 生成的流将以可预测的顺序生成项目。 Concat只是Merge ,maxConcurrent设置为1。
  • switch - 订阅第一个内部流。 然后,当新流到达时,“切换”到它。 基本上取消订阅前一个内部流并订阅最新的内部流。 当您只想收听最新的内部流时,请使用switch

(在我的代码中,我还实现了第四个展平运算符: concatLatest就像concat ,但是当内部流完成时,它不会跳转到队列中的下一个流,而是直接跳转到队列中的最新序列抛弃任何内部流被跳过。它有点介于concatswitch in行为之间,我发现它对于处理背压同时仍然产生结果非常有用)。

因此,通常使用.map(...).mergeAll().map(...).concatAll().map(...).switch() 它是如此常见,这三种情况有Rx方法:

  • source.flatMap(selector)等效于source.map(selector).mergeAll()
  • source.concatMap(selector)等效于source.map(selector).concatAll()
  • source.flatMapLatest(selector)等同于source.map(selector).switch()

你的守则

有了上述信息,我建议您使用switch而不是merge 正如您的代码现在所处,如果有人快速更改了值,您可以同时输出2个图像请求,如果它们以错误的顺序完成,您将最终得到错误的最终画布。 switch将取消陈旧的图像请求并切换到新的请求。

请注意,如果用户继续更快地更改值,而不是图像请求可以完成,那么他们将永远看不到任何图像,因为您将继续切换到新图像。 这是我通常使用我的concatLatest因为它会concatLatest完成一些中间请求,同时它正在努力跟上。 因人而异。

清理旧画布

当我的可观察流具有产生必须清理的资源的映射操作时,我倾向于使用scan因为它可以为我跟踪先前的值(我不需要设置闭包变量来保存先前的值):

canvasState
    .filter(function (c) { return !!c; })
    .scan({}, function (acc, canvas) {
        return { previous: acc.canvas, canvas: canvas };
    })
    .subscribe(function (v) {
        // remove the old canvas
        if (v.previous) { document.body.removeChild(v.previous); }

        // add the new one
        document.body.appendChild(v.canvas);
    });

暂无
暂无

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

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