RxJS Observable fire onCompleted经过多次异步操作

时间:2016-10-06 12:37:46

标签: javascript asynchronous rxjs observable

我正在尝试创建一个observable,它可以从许多异步操作(来自Jenkins服务器的http请求)中生成值,这将使订阅者在完成所有操作后知道。我觉得我必须误解某些东西,因为这不符合我的期望。

'use strict';

let Rx = require('rx');
let _ = require('lodash');
let values = [
    {'id': 1, 'status': true},
    {'id': 2, 'status': true},
    {'id': 3, 'status': true}
];

function valuesObservable() {

    return Rx.Observable.create(function(observer) {
        _.map(values, function(value) {
            var millisecondsToWait = 1000;
            setTimeout(function() { // just using setTimeout here to construct the example
                console.log("Sending value: ", value);
                observer.onNext(value)
            }, millisecondsToWait);
        });
        console.log("valuesObservable Sending onCompleted");
        observer.onCompleted()
    });
}

let observer = Rx.Observer.create((data) => {
    console.log("Received Data: ", data);
    // do something with the info
}, (error) => {
    console.log("Error: ", error);
}, () => {
    console.log("DONE!");
    // do something else once done
});

valuesObservable().subscribe(observer);

运行它,我得到输出:

valuesObservable Sending onCompleted
DONE!
Sending value:  { id: 1, status: true }
Sending value:  { id: 2, status: true }
Sending value:  { id: 3, status: true }

虽然我希望看到的更像是:

Sending value:  { id: 1, status: true }
Received Data:  { id: 1, status: true }
Sending value:  { id: 2, status: true }
Received Data:  { id: 2, status: true }
Sending value:  { id: 3, status: true }
Received Data:  { id: 3, status: true }
valuesObservable Sending onCompleted
DONE!

我实际上并不关心列表中项目的顺序,我只想让观察者接收它们。

我相信发生的事情是Javascript异步触发超时功能,并立即进入observer.onCompleted()行。一旦订阅观察者收到onCompleted事件(是正确的词?),它就会决定它已经完成并自行处理。然后,当异步操作完成并且observable触发onNext时,观察者不再存在以对它们采取任何操作。

如果我说得对,我仍然难以理解如何让它按照我想要的方式行事。我没有意识到,我偶然发现了反模式吗?是否有更好的方法来处理这整件事?

编辑:

由于我使用setTimeout来构造我的示例,我意识到我可以通过给observable一个超时来部分地解决我的问题。

function valuesObservable() {

    return Rx.Observable.create(function(observer) {
        let observableTimeout = 10000;
        setTimeout(function() {
            console.log("valuesObservable Sending onCompleted");
            observer.onCompleted();
        }, observableTimeout);
        _.map(values, function(value) {
            let millisecondsToWait = 1000;
            setTimeout(function() {
                console.log("Sending value: ", value);
                observer.onNext(value)
            }, millisecondsToWait);
        });
    });
}

这按照我想要的顺序(数据,然后完成)从observable获取所有信息,但是根据超时的选择,我可能会遗漏一些数据,或者必须等待很长时间才能完成事件。这只是我必须忍受的异步编程的固有问题吗?

3 个答案:

答案 0 :(得分:2)

是的,有更好的方法。现在的问题是,您依靠时间延迟来实现同步,实际上您可以使用Observable运算符来代替。

第一步是不要直接使用setTimeout。而是使用timer

Rx.Observable.timer(waitTime);

接下来,您可以值数组提升到一个Observable中,以便通过执行以下操作将每个值作为事件发出:

Rx.Observable.from(values);

最后,您可以使用flatMap将这些值转换为Observables并将其展平为最终序列。结果是Observable每次发出一个源timers时发出,并在所有源Observable完成时完成。

Rx.Observable.from(values)
  .flatMap(
    // Map the value into a stream
    value => Rx.Observable.timer(waitTime),
    // This function maps the value returned from the timer Observable
    // back into the original value you wanted to emit
    value => value
  )

因此,完整的valuesObservable函数将如下所示:

function valuesObservable(values) {
  return Rx.Observable.from(values)
    .flatMap(
      value => Rx.Observable.timer(waitTime),
      value => value
    )
    .do(
      x => console.log(`Sending value: ${value}`),
      null,
      () => console.log('Sending values completed')
    );
}

请注意,如果您没有使用演示流,上面的内容也可以正常工作,即如果您有真正的http流,您甚至可以使用merge(或concat保留顺序)来简化

Rx.Observable.from(streams)
    .flatMap(stream => stream);

// OR
Rx.Observable.from(streams).merge();

// Or simply
Rx.Observable.mergeAll(streams);

答案 1 :(得分:0)

构造可观察对象的最佳方法是使用现有基元,然后使用现有运算符的组合。这避免了一些令人头疼的问题(取消订阅,错误管理等)。然后Rx.Observable.create当其他任何东西都不适合你的用例时肯定是有用的。我想知道generateWithAbsoluteTime是否合适。

无论如何,你遇到的问题是你在发送数据之前完成你的观察者。所以基本上你需要提出一个更好的完成信号。也许:

  • 如果没有发出新值,则在发出最后一个值后x秒完成
  • 当值等于某个'结束'值时完成

答案 2 :(得分:0)

感谢@paulpdaniels,这是完成我想要的最终代码,包括对Jenkins的调用:

'use strict';

let Rx = require('rx');
let jenkinsapi = require('jenkins'); // https://github.com/silas/node-jenkins/issues
let jenkinsOpts = {
    "baseUrl": "http://localhost:8080",
    "options": {"strictSSL": false},
    "job": "my-jenkins-job",
    "username": "jenkins",
    "apiToken": "f4abcdef012345678917a"
};
let jenkins = jenkinsapi(JSON.parse(JSON.stringify(jenkinsOpts)));

function jobInfoObservable(jenkins, jobName) {
    // returns an observable with a containing a single list of builds for a given job
    let selector = {tree: 'builds[number,url]'};

    return Rx.Observable.fromNodeCallback(function(callback) {
        jenkins.job.get(jobName, selector, callback);
    })();
}

function buildIDObservable(jenkins, jobName) {
    // returns an observable containing a stream of individual build IDs for a given job
    return jobInfoObservable(jenkins, jobName).flatMap(function(jobInfo) {
        return Rx.Observable.from(jobInfo.builds)
    });
}

function buildInfoObservable(jenkins, jobName) {
    // returns an observable containing a stream of http response for each build in the history for this job
    let buildIDStream = buildIDObservable(jenkins, jobName);
    let selector = {'tree': 'actions[parameters[name,value]],building,description,displayName,duration,estimatedDuration,executor,id,number,result,timestamp,url'};

    return buildIDStream.flatMap(function(buildID) {
        return Rx.Observable.fromNodeCallback(function(callback) {
            jenkins.build.get(jobName, buildID.number, selector, callback);
        })();
    });
}

let observer = Rx.Observer.create((data) => {
    console.log("Received Data: ", data);
    // do something with the info
}, (error) => {
    console.log("Error: ", error);
}, () => {
    console.log("DONE!");
    // do something else once done
});

buildInfoObservable(jenkins, jenkinsOpts.job).subscribe(observer);

依靠Rx内置运算符,我设法完全避免搞乱时序逻辑。这比嵌套多个Rx.Observable.create语句要清晰得多。