Executing two observables sequentially and wait for both to complete - rxjs

I want the done to print only after the first and second is printed.
const obs1 = new Observable<any>((observer) => {
setTimeout(() => {
console.log('first');
observer.next();
observer.complete();
}, 10000);
});
const obs2 = new Observable<any>((observer) => {
setTimeout(() => {
console.log('second');
observer.next();
observer.complete();
}, 1000);
});
from([obs1, obs2]).pipe(concatAll()).subscribe(() => {
console.log('done');
});

You don't complete any of the two source Observables so no operator can know what you consider as "done". This means you could use merge or combineLatest and only handle next notifications.
However, if you know they'll always emit just once you can complete each source and then use forkJoin or concat:
const obs1 = new Observable<any>((observer) => {
setTimeout(() => {
console.log('first');
observer.next();
observer.complete();
}, 10000);
});
...
concat(obs1, obs2).subscribe({
complete: () => {
console.log('done');
}
});

Related

how to access previous mergeMap values from rxjs

I am learning to use RXJS. In this scenario, I am chaining a few async requests using rxjs. At the last mergeMap, I'd like to have access to the first mergeMap's params. I have explored the option using Global or withLatest, but neither options seem to be the right fit here.
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => {
return readCSVFile(gauge.id);
}),
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gauge.id))),
catchError(error => of(`Bad Promise: ${error}`))
);
readCSVFile is an async request which returns an observable to read CSV from a remote server.
readStringToArray is another async request which returns an observable to convert string to Arrays
transposeArray just does the transpose
uploadToDB is async DB request, which needs gague.id from the first mergeMap.
How do I get that? It would be great to take some advice on why the way I am doing it is bad.
For now, I am just passing the ID layer by layer, but it doesn't feel to be correct.
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => readCSVFile(gauge.id)),
mergeMap(({ data, gaugeId }: any) => readStringToArray(data, gaugeId)),
map(({ data, gaugeId }) => transposeArray(data, gaugeId)),
mergeMap(({ data, gaugeId }) => uploadToDB(data, gaugeId)),
catchError(error => of(`Bad Promise: ${error}`))
);
Why don't you do simply this?
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => readCSVFile(gauge.id).pipe(
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gauge.id)))
)),
catchError(error => of(`Bad Promise: ${error}`))
);
You can also wrap the inner observable in a function:
uploadCSVFilesFromGaugeID(gaugeID): Observable<void> {
return readCSVFile(gaugeID).pipe(
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gaugeID))
);
}
In order to do this at the end:
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => uploadCSVFileFromGaugeID(gauge.id)),
catchError(error => of(`Bad Promise: ${error}`))
);
MergeMap requires all observable inputs; else, previous values may be returned.
It is a difficult job to concatenate and display the merging response. But here is a straightforward example I made so you can have a better idea. How do we easily perform sophisticated merging.
async playWithBbservable() {
const observable1 = new Observable((subscriber) => {
subscriber.next(this.test1());
});
const observable2 = new Observable((subscriber) => {
subscriber.next(this.test2());
});
const observable3 = new Observable((subscriber) => {
setTimeout(() => {
subscriber.next(this.test3());
subscriber.complete();
}, 1000);
});
console.log('just before subscribe');
let result = observable1.pipe(
mergeMap((val: any) => {
return observable2.pipe(
mergeMap((val2: any) => {
return observable3.pipe(
map((val3: any) => {
console.log(`${val} ${val2} ${val3}`);
})
);
})
);
})
);
result.subscribe({
next(x) {
console.log('got value ' + x);
},
error(err) {
console.error('something wrong occurred: ' + err);
},
complete() {
console.log('done');
},
});
console.log('just after subscribe');
}
test1() {
return 'ABC';
}
test2() {
return 'PQR';
}
test3() {
return 'ZYX';
}

RxJS: deferred inner-observables still run even when the outer-observable has no subscription left

I just realized that inner-observables (like those defined in a mergeMap or switchMap operator) do not "stop" even when the outer-observable has no subscription left.
For a better example, let's show some code:
const {
Subject,
of: obsOf,
concat: obsConcat,
defer,
} = require("rxjs");
const {
finalize,
mergeMap,
tap,
takeUntil,
} = require("rxjs/operators");
const subject = new Subject();
obsOf(null).pipe(
mergeMap(() =>
obsConcat(
defer(() => {
console.log("side-effect 1");
return obsOf(1);
}),
defer(() => {
console.log("side-effect 2");
return obsOf(2);
}),
defer(() => {
console.log("side-effect 3");
return obsOf(3);
})
)
),
finalize(() => {
console.log("finalized");
})
)
.pipe(
takeUntil(subject),
tap((i) => {
if (i === 2) {
subject.next();
}
})
).subscribe(
(i) => { console.log("next", i); },
(e) => { console.log("error", e); },
() => { console.log("complete"); },
);
// Ouput:
// > side-effect 1
// > next 1
// > side-effect 2
// > complete
// > finalized
// > side-effect 3
The fact that the side-effect 3 line is logged is weird since the outer observable already called finalize.
As all those side-effects are in a defer, they could perfectly be avoided after unsubscription. From my point-of-view, those side-effects provide no value at all.
Any idea why RxJS still execute those ?
This is unfortunately by design (as of RxJS 6) - concat will buffer the observables and will subscribe to each buffered one even after you unsubscribe (if the subscription is closed it will subscribe and immediately unsubscribe).
You have to prevent the observables from getting buffered...
obsOf(null).pipe(
mergeMap(() => obsOf(
defer(() => {
console.log("side-effect 1");
return obsOf(1);
}),
defer(() => {
console.log("side-effect 2");
return obsOf(2);
}),
defer(() => {
console.log("side-effect 3");
return obsOf(3);
})
)),
concatAll(),
finalize(() => {
console.log("finalized");
}),
takeUntil(subject),
tap((i) => {
if (i === 2) {
subject.next();
}
})
).subscribe(
(i) => { console.log("next", i); },
(e) => { console.log("error", e); },
() => { console.log("complete"); },
);
One could think the code above works, but only until you delay one of the observables. Replace obsOf(1) with timer(100).pipe(mapTo(1)); and behavior is exactly the same.
The only workaround is to make sure you are not buffering anything (mean don't use concat* operators) or limit observable production some other way (use separate Subject and control the production manually).

how to cancel promises with bluebird

I think I misunderstand how promise cancellation with bluebird works. I wrote a test that demonstrates this. How do I make it green? Thanks:
describe('cancellation tests', () => {
function fakeFetch() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 100);
});
}
function awaitAndAddOne(p1) {
return p1.then(res => res + 1);
}
it('`cancel` cancels promises higher up the chain', () => {
const p1 = fakeFetch();
const p2 = awaitAndAddOne(p1);
expect(p2.isCancellable()).toBeTruthy();
p2.cancel();
return p2
.then(() => {
console.error('then');
})
.catch(err => {
console.error(err);
})
.finally(() => {
expect(p2.isCancelled()).toBeTruthy(); // Expected value to be truthy, instead received false
expect(p1.isCancelled()).toBeTruthy();
});
});
});
From here:
The cancellation feature is by default turned off, you can enable it using Promise.config.
Seems like you didn't enable the cancellation flag on the Promise itself:
Promise.config({
cancellation: true
});
describe(...
#Karen if correct. But the issue is that your test is also a bit wrong
If you look at the isCancellable method
Promise.prototype.isCancellable = function() {
return this.isPending() && !this.isCancelled();
};
This is just checking if the promise is pending and is not already cancelled. This doesn't mean then cancellation is enabled.
http://bluebirdjs.com/docs/api/cancellation.html
If you see the above url, it quotes below
The cancellation feature is by default turned off, you can enable it using Promise.config.
And if you look at the cancel method
Promise.prototype["break"] = Promise.prototype.cancel = function() {
if (!debug.cancellation()) return this._warn("cancellation is disabled");
Now if I update your test correct like below
var Promise = require("bluebird");
var expect = require("expect");
describe('cancellation tests', () => {
function fakeFetch() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 100);
});
}
function awaitAndAddOne(p1) {
return p1.then(res => res + 1);
}
it('`cancel` cancels promises higher up the chain', () => {
const p1 = fakeFetch();
const p2 = awaitAndAddOne(p1);
value = p2.isCancellable();
expect(p2.isCancellable()).toBeTruthy();
p2.cancel();
expect(p2.isCancelled()).toBeTruthy(); // Expected value to be truthy, instead received false
expect(p1.isCancelled()).toBeTruthy();
});
});
You can see that cancellation is not enable and it executes the warning code
The execution results fail as expected
spec.js:46
cancellation tests
spec.js:46
1) `cancel` cancels promises higher up the chain
spec.js:78
0 passing (3m)
base.js:354
1 failing
base.js:370
1) cancellation tests
base.js:257
`cancel` cancels promises higher up the chain:
Error: expect(received).toBeTruthy()
Expected value to be truthy, instead received
false
at Context.it (test/index.test.js:37:36)
Now if you update the code to enable cancellation
var Promise = require("bluebird");
var expect = require("expect");
Promise.config({
cancellation: true
});
describe('cancellation tests', () => {
function fakeFetch() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 100);
});
}
function awaitAndAddOne(p1) {
return p1.then(res => res + 1);
}
it('`cancel` cancels promises higher up the chain', () => {
const p1 = fakeFetch();
const p2 = awaitAndAddOne(p1);
value = p2.isCancellable();
expect(p2.isCancellable()).toBeTruthy();
p2.cancel();
expect(p2.isCancelled()).toBeTruthy(); // Expected value to be truthy, instead received false
expect(p1.isCancelled()).toBeTruthy();
});
});
It works!

Wait for n executions of a method and then continue after complete

I have a function that returns promise:
Setup.zoomIn() : Promise<void> {...}
I would like to use rxjs to invoke that function 5 times with delay of 1 second between each, like this:
let obs = Observable.create(observer => {
let count = 0;
setTimeout(() => {
Setup.zoomIn();
count++;
observer.next();
}, 1000);
if (count === 5) {observer.complete();}
};
obs.subscribe(() =>
console.log('zoomed out');
)};
Only and only when that is executed I would like to continue with execution to perform further steps:
obs.toPromise.then(() => {
// do some stuff here but only after zoom has been invoked 5 times
})
Create a list of observables for zoomIns functions and concat them with another Observable.
function zoomIn(i) {
return new Promise(res => {
setTimeout(()=>res(i), 1000);
});
};
function anotherPromise() {
return Rx.Observable.defer(()=> {
return new Promise(res => {
setTimeout(()=>res('anotherPromise'), 3000);
});
});
}
const zoonInList = Array(5).fill(0).map((x, i)=>i).map(i=>
Rx.Observable.defer(()=> {
return zoomIn(i);
})
);
Rx.Observable.concat(...zoonInList, anotherPromise())
.subscribe(x=>console.log(x))

rxjs, subscription.unsubscribe is not a function?

I don't know why subscription.unsubscribe is not a function.
I can't figure out.
const Rx = require("rx");
const observable = Rx.Observable.create(function(observer) {
observer.next(1);
observer.next(2);
const intervalId = setInterval(() => {
observer.next('nmb');
}, 1000);
return function unsubscribe() {
clearInterval(intervalId);
}
});
const subscription = observable.subscribe(x => console.log(x));
setTimeout(() => {
subscription.unsubscribe();
}, 5000);
This issue is encountered when writing code against the rxjs 5 api but referencing rxjs 4.

Resources