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

Javascript RxJS:如何在传递下一个有效值之前进行一些清理?,javascript,rxjs,Javascript,Rxjs,我必须合并流以获得URL来加载图像:一个流用于删除事件,另一个流用于文件输入更改。在每个新路径上,我加载此图像并将其绘制到画布上。此画布被传递到另一个流中。看起来是这样的: // prevent browsers default behavior for dropTargetElement [ 'drop', 'dragover' ].forEach(function(eventName) { Rx.Observable.fromEvent(dropTargetElement, eventN

我必须合并流以获得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);
});
var canvasState = new Rx.BehaviorSubject(undefined);

// draw image
canvasState.filter(function(canvas) {
  return !!canvas;
}).subscribe(function drawImage(canvas) {
  document.body.appendChild(canvas);
});
(附带问题:是否“允许”返回
map
中的可见光?)

我的
canvasState
如下所示:

// 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);
});
var canvasState = new Rx.BehaviorSubject(undefined);

// draw image
canvasState.filter(function(canvas) {
  return !!canvas;
}).subscribe(function drawImage(canvas) {
  document.body.appendChild(canvas);
});
如您所见,如果画布值为truthy,我会将画布附加到主体。但每次有新的画布进来,我都想把旧的移走。实现这一目标的最佳方式是什么?这样的事情可能吗

// draw image
canvasState.filter(function(canvas) {
  return !!canvas;
}).beforeNext(function(oldCanvas) {
  // remove old one
}).subscribe(function drawImage(canvas) {
  document.body.appendChild(canvas);
});
嵌套观测值 是的,
映射
操作返回一个可观察值是正常的。这将为您提供一个可观察流的可观察流。有3种操作可将嵌套的可观测水平展平:

  • mergeAll-订阅所有到达的内部流。您通常会同时订阅多个流,并且最终的流通常会混合来自不同内部流的结果。您可以提供
    maxConcurrent
    参数来限制并发内部订阅的数量。当达到限制时,新的内部可观察对象排队,直到前一个内部可观察对象完成后才订阅
  • concatAll-按顺序订阅每个内部可观察流。结果流将以可预测的顺序生成项目。Concat只是
    Merge
    ,maxConcurrent设置为1
  • 开关-订阅第一个内部流。然后,当一个新的流到达时,“切换”到它。基本上取消订阅以前的内部流,并订阅最新的内部流。当您只想收听最新的内部流时,请使用
    开关
(在我的代码中,我还实现了第四个展平操作符:
concatLatest
,它类似于
concat
,但是当一个内部流完成时,它不会跳到队列中的下一个流,而是直接跳到队列中最近的序列,丢弃所有跳过的内部流ncat和
开关
在行为中,我发现它在处理背压时非常有用,同时仍能产生结果)

因此,拥有
.map(…).mergeAll()
.map(…).concatAll()
.map(…).switch()
是非常常见的。非常常见的是,这三种情况下都有Rx方法:

  • source.flatMap(选择器)
    相当于
    source.map(选择器).mergeAll()
  • source.concatMap(选择器)
    相当于
    source.map(选择器).concatAll()
  • source.flatMapLatest(选择器)
    相当于
    source.map(选择器).switch()
你的代码 根据以上信息,我建议您使用
switch
而不是
merge
。就您的代码而言,如果有人快速更改值,您可能会同时发出两个图像请求,如果它们以错误的顺序完成,您将得到错误的最终画布。
switch
将取消过时的图像e请求并切换到新的

请注意,如果用户更改值的速度一直快于图像请求完成的速度,那么他们将永远看不到任何图像,因为您将继续切换到新图像。这就是我通常使用my
concatLatest
的地方,因为它会在尝试跟上.YMMV的同时不时完成一些中间请求

清理旧画布 当我的可观察流有一个map操作,该操作生成必须清理的资源时,我倾向于使用
scan
,因为它可以为我跟踪上一个值(我不需要制作一个闭包变量来保存上一个值):


请注意,您可能还需要侦听映像的错误事件,以便在映像无法加载时可以采取措施…您是对的!感谢您的提示。有趣的问题,回答得很好。请注意,您还创建了其他资源,但没有清理:事件侦听器本身。我只是在查找此问题时写到了这一点搜索如何清理这些订阅…:-)这是一个很好的解释性答案,它极大地帮助清理了我的代码,并帮助我理解RxJS。一切工作都完美无缺。太感谢你了!啊,那
扫描
以获得上一个值很好!concatLatest()听起来像是我一直想要的缺少的运算符,您是否有机会共享该实现?