簡體   English   中英

RxJS Observable fire on在許多異步操作后完成

[英]RxJS Observable fire onCompleted after a number of async actions

我正在嘗試創建一個可觀察到的對象,該對象從許多異步操作(來自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事件(這是正確的單詞嗎?),它便確定已完成並自行處理。 然后,當異步動作完成並且可觀察到的觸發onNext ,觀察者將不再存在以對其執行任何動作。

如果我是對的,我仍然對如何使其表現出自己的狀態感到困惑。 我有沒有偶然發現一個反模式嗎? 是否有更好的方法來處理整個問題?


編輯:

由於我使用setTimeout構造了示例,因此我意識到可以通過給可觀察到的超時使用它來部分解決我的問題。

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運算符來代替。

第一步是遠離使用setTimeout 而是使用timer

Rx.Observable.timer(waitTime);

接下來,您可以通過以下操作 values數組提升為Observable,以便將每個值作為事件發出:

Rx.Observable.from(values);

最后,您將使用flatMap將這些值轉換為Observables並將其展平為最終序列。 結果是一個Observable ,它在每次源timers發出時都發出,並在所有源Observables完成時完成。

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);

構造可觀察對象的最佳方法是使用現有的圖元,然后使用現有運算符的組合。 這樣可以避免一些麻煩(取消訂閱,錯誤管理等)。 然后,當沒有其他適合您的用例時, Rx.Observable.create肯定有用。 我想知道generateWithAbsoluteTime是否適合。

無論如何,這里遇到的問題是您在向觀察者發送數據之前先完成觀察。 因此,基本上,您需要提出一個更好的完成信號。 也許 :

  • 如果沒有發出新值,則在發出最后一個值之后完成x秒
  • 當值等於某個“結束”值時完成

多虧@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語句干凈得多。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM