并行激活异步请求,但使用rxjs按顺序获取结果

时间:2015-12-28 02:17:14

标签: javascript rxjs

例如:

使用jquery ajax在parrallel中获取5页。当page2返回时,什么都不做。当page1返回时,使用page1和page2执行某些操作。

// assume there is some operator that can do this, 
// then it might look like this?
Rx.Observable.range(1, 5).
someOperator(function(page) {
  return Rx.Observable.defer( () => $.get(page) );
}).scan(function(preVal, curItem) {
  preVal.push(curItem);
  return preVal;
}, []);

2 个答案:

答案 0 :(得分:2)

存在forkJoin运算符run all observable sequences in parallel and collect their last elements.(引自文档)。但是如果你使用那个,你将不得不等待所有5个承诺解决,或者5个中的一个出错。它相当于RSVP.alljQuery.when。因此,一旦你拥有第二个,就不会允许你做某事。无论如何我都会提到它,以防它在另一种情况下对你有用。

另一种可能性是使用concatMap,这将允许您按顺序接收已解决的承诺。但是,我不清楚它们是否会并行启动,第二个承诺应该在第一个承诺解决后开始。

我能想到的最后一个选项是使用merge(2),它应该并行运行两个承诺,并且在任何时候它们只会被两个承诺“启动”。

现在,如果您不使用defer,并且使用concatMap,我相信您应该启动所有AJAX请求,并且仍然是正确的排序。所以你可以写:

.concatMap(function(page) {
  return $.get(page);
})

相关文件:

答案 1 :(得分:1)

concatMap保持顺序,但按顺序处理元素。

mergeMap不保留顺序,但它并行运行。

我在下面创建了运算符mergeMapAsync,以便并行生成流程元素(例如页面下载),但按顺序发出。它甚至支持限制(例如,并行下载最多6页)。

Rx.Observable.prototype.mergeMapAsync = mergeMapAsync;
function mergeMapAsync(func, concurrent) {
    return new Rx.Observable(observer => {
        let outputIndex = 0;
        const inputLen = this.array ? this.array.length : this.source.array.length;
        const responses = new Array(inputLen);

        const merged = this.map((value, index) => ({ value, index })) // Add index to input value.
            .mergeMap(value => {
                return Rx.Observable.fromPromise(new Promise(resolve => {
                    console.log(`${now()}: Call func for ${value.value}`);  
                    // Return func retVal and index.
                    func(value.value).then(retVal => {
                        resolve({ value: retVal, index: value.index });
                    });
                }));
            }, concurrent);

        const mergeObserver = {
            next: (x) => {
                console.log(`${now()}: Promise returned for ${x.value}`);
                responses[x.index] = x.value;

                // Emit in order using outputIndex.
                for (let i = outputIndex, len = responses.length; i < len; i++) {
                    if (typeof responses[i] !== "undefined") {
                        observer.next(responses[i]);
                        outputIndex = i + 1;
                    } else {
                        break;
                    }
                }
            },
            error: (err) => observer.error(err),
            complete: () => observer.complete()
        };
        return merged.subscribe(mergeObserver);
    });
};

// ----------------------------------------
const CONCURRENT = 3;
var start = Date.now();
var now = () => Date.now() - start;

const array = ["a", "b", "c", "d", "e"];
Rx.Observable.from(array)
    .mergeMapAsync(value => getData(value), CONCURRENT)
    .finally(() => console.log(`${now()}: End`))
    .subscribe(value => {
        console.log(`${now()}: ${value}`); // getData
    });

function getData(input) {
    const delayMin = 500; // ms
    const delayMax = 2000; // ms

    return new Promise(resolve => {
        setTimeout(() => resolve(`${input}+`), Math.floor(Math.random() * delayMax) + delayMin);
    });
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>mergeMapAsync</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.8/Rx.min.js"></script>
</head>
<body>

</body>
</html>