For example:
Get 5 pages in parrallel using jquery ajax. When page2 return, do nothing. When page1 return, do something with page1 and page2.
// assu
concatMap keeps the order, but processes elements in sequence.
mergeMap does not keep the order, but it runs in parallel.
I've created the operator mergeMapAsync below to make process elements (e.g. page downloads) in parallel but emit in order. It even supports throttling (e.g. download max. 6 pages in parallel).
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);
});
}
mergeMapAsync