The best way (though complicated) is to create a new operator, similar to tap
but that does something before and after the value is emitted.
You can see it working in the example (it's in ES6, as SO code snippets don't accept TypeScript, but you'll get the idea)
function wrap(before, after) {
return function wrapOperatorFunction(source) {
return source.lift(new WrapOperator(before, after));
};
}
class WrapOperator {
constructor(before, after) {
this.before = before;
this.after = after;
}
call(subscriber, source) {
return source.subscribe(new WrapSubscriber(subscriber, this.before, this.after));
}
}
class WrapSubscriber extends Rx.Subscriber {
constructor(destination, before, after) {
super(destination);
this.before = before;
this.after = after;
}
_next(value) {
this.before ? this.before(value) : null;
this.destination.next(value);
this.after ? this.after(value) : null;
}
}
// Now:
const observable = Rx.Observable.from([1, 2, 3, 4]);
observable.pipe(
wrap(value => console.log('before', value), value => console.log('after', value))
).subscribe(value => console.log('value emitted', value), null, () => console.log('complete'));
// For what you want:
// let's simulate that, for each value in the array, we'll fetch something from an external service:
// we want the before to be executed when we make the request, and the after to be executed when it finishes. In this // case, we just combine two wrap operators and flatMap, for example:
observable.pipe(
wrap(value => console.log('BEFORE REQUEST', value)),
Rx.operators.flatMap(value => {
const subject = new Rx.Subject();
setTimeout(() => { subject.next(value); subject.complete(); }, 5000);
return subject;
}),
wrap(undefined, value => console.log('AFTER REQUEST', value))
).subscribe(value => console.log('value emitted', value), null, () => console.log('complete'));
<script src="https://unpkg.com/@reactivex/rxjs@5.5.0/dist/global/Rx.js"></script>
As stated, maybe a bit complicated, but it integrates seamlessly with RxJS operators and it is always a good example to know how to create our own operators :-)
For what you say in your comment, you can check the last example. There, I combine two wrap
operators. The first one only uses the before
callback, so it only executes something before a value is been emitted. As you see, because the source observable is from an array, the four before
callbacks are executed immediately. Then, we apply flatMap
. To it, we apply a new wrap
, but this time with just the after
callback. So, this callback is only called after the observables returned by flatMap
yield their values.
Of course, if instead an observable from an array, you'd have one made from an event listener, you'd have:
before
callback is executed just before the event fired is pushed by the observable.
- The asynchronous observable executes.
- The asynchronous observable yields values after a time t.
- The
after
callback is executed.
This is where having operators pays off, as they're easily combined. Hope this suits you.