假设我有以下基于异步回调的“无限”序列,我会在一段时间后取消:
'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);
答案 0 :(得分:0)
每次完成时你似乎都在重复一个动作。对于expand
或repeatWhen
来说,这似乎是一个很好的用例。
通常情况如下:
Rx.Observable.just(false).expand(_ => {
return cancelled ? Rx.Observable.empty() : Rx.Observable.fromCallback(asyncAction)
})
您可以在任何时间点将cancelled
置为true,当前操作完成时,它会停止循环。没有测试过,所以我很想知道它到底是否有效。
您可以查看有关民意调查的类似问题:
文档:
文档链接适用于Rxjs 4,但与v5相比应该没有太大的变化