将无限异步回调序列转换为Observable序列?

时间:2016-09-01 03:22:12

标签: javascript rxjs rxjs5

假设我有以下基于异步回调的“无限”序列,我会在一段时间后取消:

'use strict';

const timers = require('timers');

let cancelled = false;

function asyncOperation(callback) {
  const delayMsec = Math.floor(Math.random() * 10000) + 1;
  console.log(`Taking ${delayMsec}msec to process...`);
  timers.setTimeout(callback, delayMsec, null, delayMsec);
}

function cancellableSequence(callback) {
  asyncOperation((error, processTime) => {
    console.log('Did stuff');
    if (!cancelled) {
      process.nextTick(() => { cancellableSequence(callback); });
    } else {
      callback(null, processTime);
    }
  });
}

cancellableSequence((error, lastProcessTime) => {
  console.log('Cancelled');
});

timers.setTimeout(() => { cancelled = true; }, 0);

asyncOperation将执行并至少回拨一次,取消消息不会立即显示,而是在asyncOperation完成后显示。对asyncOperation的调用次数取决于内部delayMsec值和最后传递给setTimeout()的延迟参数(试图表明这些是可变的)。

我开始学习RxJS5,并认为有可能将其转换为Observable序列(“oooh,Observable订阅可以取消订阅()d - 看起来很整洁!”)。

但是,我尝试将cancellableSequence转换为ES6生成器(如何使其无限?)产生Observable.bindNodeCallback(asyncOperation)()会导致立即收益,这在我的情况下是不受欢迎的行为。

我无法使用Observable.delay()Observable.timer(),因为我没有已知的一致间隔。 (asyncOperation中的Math.random(...)试图表明我作为调用者不控制时间,并且回调发生在“某个未知的时间之后”。)

我失败的尝试:

'use strict';

const timers = require('timers');
const Rx = require('rxjs/Rx');

function asyncOperation(callback) {
  const delayMsec = Math.floor(Math.random() * 10000) + 1;
  console.log(`Taking ${delayMsec}msec to process...`);
  timers.setTimeout(callback, delayMsec, null, delayMsec);
}

const operationAsObservable = Rx.Observable.bindNodeCallback(asyncOperation);
function* generator() {
  while (true) {
    console.log('Yielding...');
    yield operationAsObservable();
  }
}

Rx.Observable.from(generator()).take(2).mergeMap(x => x).subscribe(
  x => console.log(`Process took: ${x}msec`),
  e => console.log(`Error: ${e}`),
  c => console.log('Complete')
)

输出结果如下:

Yielding...
Taking 2698msec to process...
Yielding...
Taking 2240msec to process...
Process took: 2240msec
Process took: 2698msec
Complete

收益率立即发生。 Process took: xxx输出会在您预期时发生(分别在2240和2698ms之后)。

(平心而论,我关心收益率之间的延迟的原因是asyncOperation()这里实际上是一个速率限制令牌桶库,它控制异步回调的速率 - 这是我的一个实现喜欢保留。)

顺便说一句,我尝试用延迟取消替换take(2),但从未发生过:

const subscription = Rx.Observable.from(generator()).mergeMap(x => x).subscribe(
  x => console.log(`Process took: ${x}msec`),
  e => console.log(`Error: ${e}`),
  c => console.log('Complete')
)

console.log('Never gets here?');
timers.setTimeout(() => {
  console.log('Cancelling...');
  subscription.unsubscribe();
}, 0);

我可以尝试通过RxJS取消订阅吗? (我可以看到其他方法,例如process.exec('node', ...)作为一个单独的进程运行asyncOperation(),让我能够process.kill(..)等等,但让我们不去那里......)。

我最初的基于回调的实现是实现可取消序列的建议方法吗?

更新的解决方案:

请参阅我的回复评论@ user3743222的答案如下。这是我最终得到的结果(用Observable.expand()替换ES6生成器):

'use strict';

const timers = require('timers');
const Rx = require('rxjs/Rx');

function asyncOperation(callback) {
  const delayMsec = Math.floor(Math.random() * 10000) + 1;
  console.log(`Taking ${delayMsec}msec to process...`);
  timers.setTimeout(callback, delayMsec, null, delayMsec);
}

const operationAsObservable = Rx.Observable.bindNodeCallback(asyncOperation);

const subscription = Rx.Observable
  .defer(operationAsObservable)
  .expand(x => operationAsObservable())
  .subscribe(
    x => console.log(`Process took: ${x}msec`),
    e => console.log(`Error: ${e}`),
    c => console.log('Complete')
  );

subscription.add(() => {
  console.log('Cancelled');
});

timers.setTimeout(() => {
  console.log('Cancelling...');
  subscription.unsubscribe();
}, 0);

更新的解决方案2:

以下是我为备用RxJS4 repeatWhen()方法提出的建议:

'use strict';

const timers = require('timers');
const Rx = require('rx');

function asyncOperation(callback) {
  const delayMsec = Math.floor(Math.random() * 1000) + 1;
  console.log(`Taking ${delayMsec}msec to process...`);
  timers.setTimeout(callback, delayMsec, null, delayMsec);
}

const operationAsObservable = Rx.Observable.fromNodeCallback(asyncOperation);

const subscription = Rx.Observable
  .defer(operationAsObservable)
  .repeatWhen(x => x.takeWhile(y => true))
  .subscribe(
    x => console.log(`Process took: ${x}msec`),
    e => console.log(`Error: ${e}`),
    c => console.log('Complete')
  );

timers.setTimeout(() => {
  console.log('Cancelling...');
  subscription.dispose();
}, 10000);

1 个答案:

答案 0 :(得分:0)

每次完成时你似乎都在重复一个动作。对于expandrepeatWhen来说,这似乎是一个很好的用例。

通常情况如下:

Rx.Observable.just(false).expand(_ => {  
  return cancelled ? Rx.Observable.empty() : Rx.Observable.fromCallback(asyncAction)
})

您可以在任何时间点将cancelled置为true,当前操作完成时,它会停止循环。没有测试过,所以我很想知道它到底是否有效。

您可以查看有关民意调查的类似问题:

文档:

文档链接适用于Rxjs 4,但与v5相比应该没有太大的变化