How do I timeout an event in RxJS? - rxjs

I'm trying to detect if the mousedown event is held for a period of time before a mouseup.
I'm using timeout() on an Observable created with fromEvent() to do so, but the timeout returns both Observables.
Below, subscribing to stream returns the event if mousedown is triggered within 1 second, but it also returns 1.
var mousedown = Rx.Observable.fromEvent(target, 'mousedown');
var stream = mousedown.timeout(1000, Rx.Observable.return(1));
var sub = stream.subscribe(
function (x) {
console.log('Next: '+x);
},
function (err) {
console.log('Err: '+err);
},
function () {
console.log('Complete');
}
);
However, this works as expected:
var source = Rx.Observable.return(42)
.delay(200)
.timeout(1000, Rx.Observable.return(1));
I'd like this code to work:
var mousedown = Rx.Observable.fromEvent(target, 'mousedown');
var mouseup = Rx.Observable.fromEvent(target, 'mouseup');
var clickhold = mousedown
.flatMap(function (){
return mouseup.timeout(1000, Rx.Observable.return('hold'));
})
.filter(function (x) {
return x === 'hold';
});
clickhold.subscribe(
function (x) {
console.log('Next: '+x);
},
function (err) {
console.log('Err: '+err);
},
function () {
console.log('Complete');
}
);

Instead of using timeout, I used delay and takeUntil:
var target,
mousedown,
mouseup;
target = document.querySelector('input');
mousedown = Rx.Observable.fromEvent(target, 'mousedown');
mouseup = Rx.Observable.fromEvent(target, 'mouseup');
var clickhold = mousedown
.flatMap(function(){
// Triggered instantly after mousedown event.
return Rx.Observable
.return('hold')
.delay(1000)
// Discards the return value if by the time .delay() is complete
// mouseup event has been already completed.
.takeUntil(mouseup);
});
clickhold.subscribe(
function (x) {
console.log('Next: ' + x);
},
function (err) {
console.log('Err: ' + err);
},
function () {
console.log('Complete');
}
);
<script src='https://rawgit.com/Reactive-Extensions/RxJS/v.2.5.3/dist/rx.all.js'></script>
<input type='button' value='Button' />

You came up with a great solution on your own. Here's what I would change:
Move the inner observable (timer(...).takeUntil(...).select(...)) out of flatMap, so it isn't re-allocated for each mouse down.
You've got the rest right. For my usage, I usually retain the original mousedown event and use that instead of 'hold'. That requires returnValue and delay instead of timer and select.
var target,
mousedown,
mouseup;
target = document.querySelector('input');
mousedown = Rx.Observable.fromEvent(target, 'mousedown');
mouseup = Rx.Observable.fromEvent(target, 'mouseup');
var clickhold = mousedown
.flatMap(function (e) {
return Rx.Observable
.return(e)
.delay(1000)
.takeUntil(mouseup);
});
clickhold.subscribe(function (x) {
console.log('onNext: ', x);
});
<script src='https://rawgit.com/Reactive-Extensions/RxJS/v.2.5.3/dist/rx.all.js'></script>
<input type='button' value='Button' />
Or, for a completely different approach...
var Observable = Rx.Observable,
fromEvent = Observable.fromEvent.bind(Observable, target),
holdDelay = Observable.empty().delay(1000);
Observable
.merge(
[
fromEvent('mouseup')
.map(empty),
fromEvent('mousedown')
.map(Observable.returnValue)
.map(holdDelay.concat.bind(holdDelay))
]
)
.switchLatest();
Ok so that's weird. I'm really just giving it as food for though, and to show off that this can be done in a number of different ways.

Related

How to make polling observable detect unfullfilled promise

This observable polls getPromise() function every second. After getPromise() function returns 3 promises it stops resolving them. How do I detect that getPromise() function hasn't resolve/rejected any promise for the past, let's say, 2 seconds, and call onError handler. I've tried making it work with timeout operator to no avail. Any ideas?
Rx.Observable.interval(1000)
.switchMap(() => Rx.Observable.fromPromise(getPromise()))
.subscribe(onValue, onError);
function onValue(value){
console.log('value: ', value);
}
function onError(error){
console.log('error: ', error);
}
var getPromise = (function(){
var counter = 3;
return function(){
return new Promise(function(resolve, reject){
if(counter > 0) resolve(1);
counter--;
})
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.3.0/Rx.js"></script>
You can use the race operator that subscribes only to the first Observable that emits.
You said you want to call onError handler after 2 of inactivity. This contradicts with using switchMap which automatically unsubscribes when a new Observable is returned from its callback. So you might want to use exhaustMap instead. Also when you emit an error notification the chain unsubscribes and you'll never receive any other value. This means that you shouldn't emit the timeout as an error or use also the retry operator to automatically resubscribe (but this really depends on what you're trying to achieve).
This is you updated example that is just using the race() operator.
Rx.Observable.interval(1000)
.switchMap(() =>
Rx.Observable.race(
Rx.Observable.fromPromise(getPromise()),
Rx.Observable.timer(0, 1000).mapTo(42)
)
)
.subscribe(onValue, onError);
function onValue(value){
console.log('value: ', value);
}
function onError(error){
console.log('error: ', error);
}
var getPromise = (function(){
var counter = 3;
return function(){
return new Promise(function(resolve, reject){
if(counter > 0) resolve(1);
counter--;
})
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.3.0/Rx.js"></script>
Edit: To send a single error notification after 2 seconds of inactivity.
Rx.Observable.interval(1000)
.switchMap(() => Rx.Observable.fromPromise(getPromise()))
.timeout(2000)
.subscribe(onValue, onError);
function onValue(value){
console.log('value: ', value);
}
function onError(error){
console.log('error: ', error);
}
var getPromise = (function(){
var counter = 3;
return function(){
return new Promise(function(resolve, reject){
if(counter > 0) resolve(1);
counter--;
})
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.2.0/Rx.js"></script>
There's really a bug in 5.3.0 not directly in the timeout() operator but in scheduling async actions. https://github.com/ReactiveX/rxjs/pull/2580
Without the timeout() operator:
Rx.Observable.interval(1000)
.switchMap(() =>
Rx.Observable.race(
Rx.Observable.fromPromise(getPromise()),
Rx.Observable.timer(0, 2000).map(function(_) {
throw new Error('timeout');
})
)
)
.subscribe(onValue, onError);

Rxjs 4 simple debug with console.log

I'm quite new at rxjs stuff so please be patience :).
var source = Rx.Observable.fromEvent(document, 'keyup');
source.filter(function(x){
console.log('filter with', x);
return true;
});
var subscription = source.subscribe(
function (x) {
console.log('Next: keyup!',x.keyCode);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
whats the right way to debug inside filter
I dont see any filter with
in the console
I've also tried with
var source = Rx.Observable.fromEvent(document, 'keyup');
source.filter(function(x){
console.log('filter with', x);
return true;
});
source.do(x => console.log('do with',x));
var subscription = source.subscribe(
function (x) {
console.log('Next: keyup!',x.keyCode);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
with no lucky
Can you give me an hint please ?
source.filter() is creating a new Observable, yet you only subscribe to the original Observable, source. Observables that aren't subscribed to are not carried out
You have to do something like this:
source.filter()
.do()
.subscribe()

Rx Js - create an observer for $.ajax Complete()

I have an ajax call for an insert operation. I need to update a grid (future there might be many) once the ajax call completed. How to do this using Rx Js.
I have done something with event based but it won't work out for my case.
NOTE: save ajax call is in different script file.
var submitButton = document.querySelector('.btn-primary');
var refresh1ClickStream = Rx.Observable.fromEvent(submitButton, 'click');
var requestStream = refresh1ClickStream.startWith('startup click')
.map(function () {
var max = 10;
return 'http://localhost/.../GetPayments';
});
var responseStream = requestStream
.flatMap(function (requestUrl) {
return Rx.Observable.fromPromise($.getJSON(requestUrl));
});
function createSuggestionStream(closeClickStream) {
return closeClickStream.startWith('startup click')
.combineLatest(responseStream,
function (click, data) {
return data;
}
)
.merge(
refresh1ClickStream.map(function () {
return null;
})
)
.startWith(null);
}
var paymentStream = createSuggestionStream(refresh1ClickStream);
//var pfaStream = createSuggestionStream(refresh1ClickStream);
// Rendering ---------------------------------------------------
function renderSuggestion(payments, selector) {
//render grid
}
paymentStream.subscribe(function (payment) {
renderSuggestion(payment, 'payment1');
});
//pfaStream.subscribe(function (pfa) {
// renderSuggestion(pfa, 'tblPayFromAccount');
//});

How to use node-walk with RxJS?

Node walk presents an API with a few events like this.
walker.on('file', (root, filestats, next) => {
// next should be called to goto next file
next();
});
walker.on('end', () => {
// the end of the stream
});
Is it reactive if from the subscriber you're calling a function to inform the source to go to the next item in the stream ? Events don't wait for the subscriber to react to it, right ?
How do I transform this into a Rx Observable ?
Your best bet is to create a wrapper around it:
Rx.Observable.fromWalk = function(root, options, scheduler) {
scheduler = scheduler || Rx.Scheduler.currentThread;
return Rx.Observable.create(function(observer) {
var walker = walk.walk(root, options);
function fileHandler(x) {
observer.onNext({stats : x.stats, root : x.root});
scheduler.scheduleWithState(x, function(s, i) {
i.next();
});
}
var files = Rx.Observable.fromEvent(walker, 'file',
function(arr) {
return { root : arr[0], stats : arr[1], next : arr[2] };
});
var ended = Rx.Observable.fromEvent(walker, 'end');
return new Rx.CompositeDisposable(
files.subscribe(fileHandler),
ended.subscribe(observer.onCompleted.bind(observer))
);
});
};
I updated your example accordingly
Try Rx.Observable.fromCallback:
var walkerOn = Rx.Observable.fromCallback(walker.on, walker)
var source = walkerOn('file');
var subscription = source.subscribe(observer);

tap event fired after taphold jQuery Mobile 1.1.1

I am developing an app for iOS using Phonegap bundled with jQuery Mobile 1.1.1. I have a div on my page that is listening for both tap and taphold events.
The problem I am facing is that the tap event is fired after the taphold event once I lift my finger. How do I prevent this?
A solution is provided here but is this the only way to do this? Kinda nullifies the whole point of having two different events for tap & taphold if you need to use a boolean flag to differentiate the two.
Following is my code:
$('#pageOne').live('pageshow', function(event) {
$('#divOne').bind('taphold', function (event) {
console.log("TAP HOLD!!");
});
$('#divOne').bind('tap', function () {
console.log("TAPPED!!");
});
});
Would greatly appreciate the help. Thanks!
Simply set this at the top of your document or anywhere before you define your even:
$.event.special.tap.emitTapOnTaphold = false;
Then you can use it like this:
$('#button').on('tap',function(){
console.log('tap!');
}).on('taphold',function(){
console.log('taphold!');
});
[Tried and Tested]
I checked jQuery Mobile's implementation. They are firing the 'tap' event after 'taphold' every time on 'vmouseup'.
Workaround would be not to fire the 'tap' event if the 'taphold' has been fired. Create a custom event or modify the source as per you need as follows:
$.event.special.tap = {
tapholdThreshold: 750,
setup: function() {
var thisObject = this,
$this = $( thisObject );
$this.bind( "vmousedown", function( event ) {
if ( event.which && event.which !== 1 ) {
return false;
}
var origTarget = event.target,
origEvent = event.originalEvent,
/****************Modified Here**************************/
tapfired = false,
timer;
function clearTapTimer() {
clearTimeout( timer );
}
function clearTapHandlers() {
clearTapTimer();
$this.unbind( "vclick", clickHandler )
.unbind( "vmouseup", clearTapTimer );
$( document ).unbind( "vmousecancel", clearTapHandlers );
}
function clickHandler( event ) {
clearTapHandlers();
// ONLY trigger a 'tap' event if the start target is
// the same as the stop target.
/****************Modified Here**************************/
//if ( origTarget === event.target) {
if ( origTarget === event.target && !tapfired) {
triggerCustomEvent( thisObject, "tap", event );
}
}
$this.bind( "vmouseup", clearTapTimer )
.bind( "vclick", clickHandler );
$( document ).bind( "vmousecancel", clearTapHandlers );
timer = setTimeout( function() {
tapfired = true;/****************Modified Here**************************/
triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) );
}, $.event.special.tap.tapholdThreshold );
});
}
};
You can use stopImmediatePropagation() method of jquery to solve this issue. According to the explanation in jquery api, stopImmediatePropagation() method
"Keeps the rest of the handlers from being executed and prevents the
event from bubbling up the DOM tree."
put this in your taphold event handler... this suggestion assumes o is a jQuery object that fired the taphold
jQuery(o).one('tap click', function(){ return false; });
the binding to the one method will fire the event only once. returning false will stop the execution of that event if it was an < a > tag.
Since swipe, triggers taphold then I was able to keep it simple with:
$(c).bind("taphold",function(e){
if(e.target.wait){
e.target.wait = false;
}else{
alert("fire the taphold");
}//eo if not waiting
});
$(c).bind("swipe",function(e){
e.target.wait = true;//taphold will come next if I don't wave it off
alert(e.target.text+"("+e.target.attributes.dataId.value+") got swiped");
return false;
});
To support tap too then I'd defer the wait clear until the tap event which will also always fire.
I still have problems, with jquery-mobile's taphold, I solved the problem of the click called after taphold, putting a timeout on the element.
JQM 1.4 with emitTapOnTaphold = false;
Example:
$(".element").on("taphold", function () {
        // function her
         setTimeout (function () {
             $(this).blur();
         400);
});
$.event.special.tap = {
tapholdThreshold: 750,
setup: function() {
var thisObject = this,
$this = $( thisObject );
$this.bind( "vmousedown", function( event ) {
if ( event.which && event.which !== 1 ) {
return false;
}
var origTarget = event.target,
origEvent = event.originalEvent,
/****************Modified Here**************************/
tapfired = false,
timer;
function clearTapTimer() {
clearTimeout( timer );
}
function clearTapHandlers() {
clearTapTimer();
$this.unbind( "vclick", clickHandler )
.unbind( "vmouseup", clearTapTimer );
$( document ).unbind( "vmousecancel", clearTapHandlers );
}
function clickHandler( event ) {
clearTapHandlers();
// ONLY trigger a 'tap' event if the start target is
// the same as the stop target.
/****************Modified Here**************************/
//if ( origTarget === event.target) {
if ( origTarget === event.target && !tapfired) {
triggerCustomEvent( thisObject, "tap", event );
}
}
$this.bind( "vmouseup", clearTapTimer )
.bind( "vclick", clickHandler );
$( document ).bind( "vmousecancel", clearTapHandlers );
timer = setTimeout( function() {
tapfired = true;/****************Modified Here**************************/
triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) );
}, $.event.special.tap.tapholdThreshold );
});
}
};
#Akash Budhia: Thanks for your solutions.
It's great, sounds it work for me!

Resources