问题
I use rxjs to handle a websocket connection
var socket = Rx.Observable.webSocket('wss://echo.websocket.org')
socket.resultSelector = (e) => e.data
I want to periodically (5s) sent a ping
message and wait 3s to receive a pong
response and subscribe to the a stream if no response has been receive.
I try that without success. I admit I'm a bit lost will all the operator available to handle timeout, deboune or throttle.
// periodically send a ping message
const ping$ = Rx.Observable.interval(2000)
.timeInterval()
.do(() => socket.next('ping'))
const pong$ = socket
.filter(m => /^ping$/.test(`${m}`))
.mergeMap(
ping$.throttle(2000).map(() => Observable.throw('pong timeout'))
)
pong$.subscribe(
(msg) => console.log(`end ${msg}`),
(err) => console.log(`err ${err}`),
() => console.log(`complete`)
)
But unfortunately, no ping are send.
I've also try to achieved that using without success.
const ping$ = Rx.Observable.interval(2000)
.timeInterval()
.do(() => socket.next('ping'))
const pong$ = socket
.filter(m => /^ping$/.test(`${m}`))
const heartbeat$ = ping$
.debounceTime(5000)
.mergeMap(() => Rx.Observable.timer(5000).takeUntil(pong$))
heartbeat$.subscribe(
(msg) => console.log(`end ${msg}`),
(err) => console.log(`err ${err}`),
() => console.log(`complete`)
)
Any help appreciated.
回答1:
You can use race()
operator to always connect only to the Observable that emits first:
function sendMockPing() {
// random 0 - 5s delay
return Observable.of('pong').delay(Math.random() * 10000 / 2);
}
Observable.timer(0, 5000)
.map(i => 'ping')
.concatMap(val => {
return Observable.race(
Observable.of('timeout').delay(3000),
sendMockPing()
);
})
//.filter(response => response === 'timeout') // remove all successful responses
.subscribe(val => console.log(val));
See live demo: https://jsbin.com/lavinah/6/edit?js,console
This randomly simulates response taking 0 - 5s
. When the response takes more than 3s than Observable.of('timeout').delay(3000)
completes first and the timeout
string is passed to its observer by concatMap()
.
回答2:
I finally found a solution based on mergeMap
and takeUntil
My initial mistake was to use ping$ as an input for my heartBeat$ where I should use $pong
// define the pong$
const pong$ = socket
.filter(m => /^ping$/.test(`${m}`))
.share()
//use share() because pong$ is used twice
const heartbeat$ = pong$
.startWith('pong') // to bootstrap the stream
.debounceTime(5000) // wait for 5s after the last received pong$ value
.do(() => this.socket.next('ping')) // send a ping
.mergeMap(() => Observable.timer(3000).takeUntil(pong$))
// we merge the current stream with another one that will
// not produce value while a pong is received before the end of the
// timer
heartbeat$.subscribe(
(msg) => console.log(`handle pong timeout`),
)
回答3:
Below heartbeat$ function return an observable which you can continuously listen to
1) the latency value
of each round trip (time of socket.receive - socket.send) in every 5000ms
or
2) -1
if the round trip goes beyond the threshold (e.g. 3000ms)
You will keep receiving latency value
or -1
even though -1
has been emitted which gives you the flexibility to decide what to do ^.^
heartbeat$(pingInterval: number, pongTimeout: number) {
let start = 0;
const timer$ = timer(0, pingInterval).pipe(share());
const unsub = timer$.subscribe(() => {
start = Date.now();
this.ws.next('ping');
});
const ping$ = this.ws$.pipe(
switchMap(ws =>
ws.pipe(
filter(m => /^ping$/.test(`${m}`)),
map(() => Date.now() - start),
),
),
share(),
);
const dead$ = timer$.pipe(
switchMap(() =>
of(-1).pipe(
delay(pongTimeout),
takeUntil(ping$),
),
),
);
return merge(ping$, dead$).pipe(finalize(() => unsub.unsubscribe()));
}
heartbeat$(5000, 3000).subscribe(
(latency) => console.log(latency) // 82 83 82 -1 101 82 -1 -1 333 ...etc
)
来源:https://stackoverflow.com/questions/42115698/how-to-periodically-check-live-connection-using-rxjs