Combination of rxjs and setTimeout creates unexpected runtime behaviour - javascript

I have some problems to understand the difference between the following two snippets.
const a = Observable.create(
o => {
setTimeout(
()=>{
console.log("now 3");
o.next(3);
setTimeout(()=>{ console.log("and now 5"); o.next(5)}, 1500);}
, 1500);
}
);
const a2 = Observable.create(
o => {
setTimeout(
()=>{
console.log("here is 4");
o.next(4);
setTimeout(o.next(6), 1500);}
, 1500);
}
);
https://stackblitz.com/edit/rxjs-byqgnh?devtoolsheight=60
I removed in the second block only the console log.
The first block works like expected: first number is emitted after timeout, second number too.
The second block emitts both numbers simultanious. Can anybody explain the difference?

When you do
setTimeout(()=>{ console.log("and now 5"); o.next(5)}, 1500);
you are passing ()=>{ console.log("and now 5"); o.next(5)} as a callback function to setTimeout API, which will be probably trigger after 1.5sec. And Its fine since this is what you wanted to happen.
But when you do
setTimeout(o.next(6), 1500)
You are executing o.next(6) and emitting value right before you are passing it as a callback function to setTimeout, but when you see It's not a function signature, what o.next(6) generates is a undefined value which will be pass as a callback and chances you will get Callback must be a function error.
So, if you want o.next(6) to be passed as a callback function to setTimeout and emit value after 1.5 sec then you can pass it as
setTimeout(() => o.next(6), 1500)
or as
setTimeout(function(){
o.next(6);
}, 1500)

setTimeout takes a callback
The following with generate a runtime error
setTimeout(o.next(6), 1500);
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function.
as the return type of n.next is undefined. Observables, however, catch errors and emit them as error events for you. So you don't see that in your example.
To fix this, you could give setTimeout a callback function instead.
setTimeout(() => o.next(6), 1500);

Related

How do I build a Clock using setInterval and clearInterval?

I am trying to Write a SecondClock class, with two methods: start and reset.​
In the start invocation, the callback gets invoked every second on the "seconds hand" of the clock. Always start with 1 and don't utilize the seconds value of the current computer clock time.
The first "tick" with value 1 occurs 1 second after the initial "secondClock" invocation.
The second "tick" with value 2 occurs 2 seconds after the initial "secondClock" invocation.
and so on.
Upon reset invocation, completely stops the "clock".
Also resets the time back to the beginning.
​
class SecondClock {
constructor(cb) {
this.cb = cb;
this.IntervalId;
this.tick = 'tick';
}
start(tick){
this.IntervalId = setInterval(this.cb(tick),1000);
}
reset(){
clearInterval(this.IntervalId);
}
}
// Border Line
const clock = new SecondClock((val) => { console.log(val) });
console.log("Started Clock.");
clock.start();
setTimeout(() => {
clock.reset();
console.log("Stopped Clock after 6 seconds.");
}, 6000);
I am able to start the clock and this is what I see in the console:
'Started Clock.'
undefined
Type Error on line callback is not a function at blob: callback is not a function
Type Error on line callback is not a function at blob: callback is not a function
Type Error on line callback is not a function at blob: callback is not a function
Type Error on line callback is not a function at blob: callback is not a function
Type Error on line callback is not a function at blob: callback is not a function
Type Error on line callback is not a function at blob: callback is not a function
'Stopped Clock after 6 seconds.'
Which should Ideally be:
'Started Clock'
1
2
3
4
5
6
'Stopped Clock after 6 seconds.'
Is there any way I can leverage the Date() class to accomplish this?
After I invoke the setInterval via clock.start() statement, I am able to invoke clearInterval after 6 seconds.
I am assuming this.cb(this.tick) is able to fetch this.tick only once. But after this.cb(this.tick) is pushed into callback queue, the error is seen.
Printing 'tick' instead of numbers was my first milestone. Further, I plan to implement it with counter is what I assumed. But clearly, I found it hard to implement even with 'tick'.
Would prefer to not to change anything below the border line.
I think the pointer you need is that setInterval or setTimeout should take a function as a first argument.
setInterval( this.cb(x) , 1000 ) //unless this.cb returns a function this wont run
Option 1: create an anonymous function
var _helper = function(){
myLogic(10);
};
setInterval (_helper, 1000);//first argument is a function
Option 2: your cb can return a function
var cb = function(x){
return function(){
//logic or side-effect
};
};
setInterval (cb(10), 1000); //first argument is still a function
Option 3: Possibly by using promises/resolve

Passing setInterval() to a variable but fires off immediately

I'm trying to understand why this code fires off immediately.
const test = setInterval(() => {
console.log("test"), 2;
});
Why does it happen? I didn't even do test();
Because you need to pass 2 as an argument to setInterval - currently it's not available. Also note that the only reason anyone assigns the result of setInterval is to call clearInterval. If you want to have it run when you want, make test a function.
const test = () => setInterval(() => console.log("test"), 2);
To avoid such syntax errors we can do like this,
const test = setInterval(myTest, 2);
function myTest() {
console.log("test")
}
Make sure to stop your setInterval function when not in use,
function myStopFunction() {
clearInterval(test);
}

How to use call back to make a function execute after another in setTimeout condition

I have some code which can be mimicked by this situation
function abc(def){
setTimeout(1500,function(){
console.log('1');
}
def();
}
function def(){
console.log('2'}
}
I want 2 to be printed after 1 - how do I do that?
If you want a function to also be called after a delay... just put it inside the delay. I've added some extra console logs and fixed some of your code. Run the below example to see how it works.
function def() {
console.log('2 - def()');
}
function abc() {
console.log('before');
setTimeout(function() {
console.log('1');
def();
}, 1500);
console.log('after');
}
abc();
function abc(def){
setTimeout(1500,function(){
console.log('1');
def();
}
}
You can use async and await. Wrap the setTimeout(...) in a Promise and resolve Promise inside the callback passed to setTimeout.
async function abc(def){
await new Promise(res => {
setTimeout(function(){
console.log('1');
res();
},1500)
})
def();
}
function def(){
console.log('2')
}
abc(def)
setTimeout(function, milliseconds) takes a function has the first parameter and delay value (time in milliseconds) as the second parameter. You were passing them in the wrong order.
Also in your example def() was printing 2 and it was outside the setTimeout function, so it could not have possibly printed 2 after 1 (as 1 was being logged inside the setTimeout, which means it would be executed after the delay of 1.5 seconds).
The solution provided by Chris above is correct and would do what you wanted (print 2 and then 1)

How to abort function by timeout at JavaScript

I need to execute some function but if timeout expired I need to abort this function. I tried to use setTimeout and setImmediate. I wrote this example and used setInterval in callback for checking of working but it did't help me:
function waiter(timeout, fun) {
var functionHandler = setImmediate(fun);
var timeoutHandler = setTimeout(() => {
console.log('stoped');
clearImmediate(functionHandler);
clearTimeout(timeoutHandler);
}, timeout);
fun();
}
waiter(5000, () => {
setInterval(() => {
console.log('work');
}, 500);
});
After clearImmediate and clearTimeout my interval still working. How I understood that method doesn't guarantee that my function will be aborted.
Does anybody have an idea how to abort function execution?
UPDATE:
Yes, I know that I should call clearInterval but it's just an example for work checking. For example I should parse some big data and if it doesn't can do it by timeout I need to cancel this function execution and call something else.
The clearXXX functions don't abort functions that are currently running, they just remove functions from the async queue, so that they never start to run. JavaScript always executes code to its completion. You cannot interrupt code that's running in the main thread.
You need to name your interval
and clear it by its name. So your interval is no longer anonymous, but you've got a reference for it.
let counter = 10;
let newYearCountdown = setInterval( () => {
console.log(counter);
counter--
if (counter === 0) {
console.log("HAPPY NEW YEAR!!");
// here i clear interval by its name.
clearInterval(newYearCountdown);
}
}, 1000);

Execute the setInterval function without delay the first time

It's there a way to configure the setInterval method of javascript to execute the method immediately and then executes with the timer
It's simplest to just call the function yourself directly the first time:
foo();
setInterval(foo, delay);
However there are good reasons to avoid setInterval - in particular in some circumstances a whole load of setInterval events can arrive immediately after each other without any delay. Another reason is that if you want to stop the loop you have to explicitly call clearInterval which means you have to remember the handle returned from the original setInterval call.
So an alternative method is to have foo trigger itself for subsequent calls using setTimeout instead:
function foo() {
// do stuff
// ...
// and schedule a repeat
setTimeout(foo, delay);
}
// start the cycle
foo();
This guarantees that there is at least an interval of delay between calls. It also makes it easier to cancel the loop if required - you just don't call setTimeout when your loop termination condition is reached.
Better yet, you can wrap that all up in an immediately invoked function expression which creates the function, which then calls itself again as above, and automatically starts the loop:
(function foo() {
...
setTimeout(foo, delay);
})();
which defines the function and starts the cycle all in one go.
I'm not sure if I'm understanding you correctly, but you could easily do something like this:
setInterval(function hello() {
console.log('world');
return hello;
}(), 5000);
There's obviously any number of ways of doing this, but that's the most concise way I can think of.
I stumbled upon this question due to the same problem but none of the answers helps if you need to behave exactly like setInterval() but with the only difference that the function is called immediately at the beginning.
Here is my solution to this problem:
function setIntervalImmediately(func, interval) {
func();
return setInterval(func, interval);
}
The advantage of this solution:
existing code using setInterval can easily be adapted by substitution
works in strict mode
it works with existing named functions and closures
you can still use the return value and pass it to clearInterval() later
Example:
// create 1 second interval with immediate execution
var myInterval = setIntervalImmediately( _ => {
console.log('hello');
}, 1000);
// clear interval after 4.5 seconds
setTimeout( _ => {
clearInterval(myInterval);
}, 4500);
To be cheeky, if you really need to use setInterval then you could also replace the original setInterval. Hence, no change of code required when adding this before your existing code:
var setIntervalOrig = setInterval;
setInterval = function(func, interval) {
func();
return setIntervalOrig(func, interval);
}
Still, all advantages as listed above apply here but no substitution is necessary.
You could wrap setInterval() in a function that provides that behavior:
function instantGratification( fn, delay ) {
fn();
setInterval( fn, delay );
}
...then use it like this:
instantGratification( function() {
console.log( 'invoked' );
}, 3000);
Here's a wrapper to pretty-fy it if you need it:
(function() {
var originalSetInterval = window.setInterval;
window.setInterval = function(fn, delay, runImmediately) {
if(runImmediately) fn();
return originalSetInterval(fn, delay);
};
})();
Set the third argument of setInterval to true and it'll run for the first time immediately after calling setInterval:
setInterval(function() { console.log("hello world"); }, 5000, true);
Or omit the third argument and it will retain its original behaviour:
setInterval(function() { console.log("hello world"); }, 5000);
Some browsers support additional arguments for setInterval which this wrapper doesn't take into account; I think these are rarely used, but keep that in mind if you do need them.
Here's a simple version for novices without all the messing around. It just declares the function, calls it, then starts the interval. That's it.
//Declare your function here
function My_Function(){
console.log("foo");
}
//Call the function first
My_Function();
//Set the interval
var interval = window.setInterval( My_Function, 500 );
There's a convenient npm package called firstInterval (full disclosure, it's mine).
Many of the examples here don't include parameter handling, and changing default behaviors of setInterval in any large project is evil. From the docs:
This pattern
setInterval(callback, 1000, p1, p2);
callback(p1, p2);
is identical to
firstInterval(callback, 1000, p1, p2);
If you're old school in the browser and don't want the dependency, it's an easy cut-and-paste from the code.
I will suggest calling the functions in the following sequence
var _timer = setInterval(foo, delay, params);
foo(params)
You can also pass the _timer to the foo, if you want to clearInterval(_timer) on a certain condition
var _timer = setInterval(function() { foo(_timer, params) }, delay);
foo(_timer, params);
For someone needs to bring the outer this inside as if it's an arrow function.
(function f() {
this.emit("...");
setTimeout(f.bind(this), 1000);
}).bind(this)();
If the above producing garbage bothers you, you can make a closure instead.
(that => {
(function f() {
that.emit("...");
setTimeout(f, 1000);
})();
})(this);
Or maybe consider using the #autobind decorator depending on your code.
You can set a very small initial delay-time (e.g. 100) and set it to your desired delay-time within the function:
var delay = 100;
function foo() {
console.log("Change initial delay-time to what you want.");
delay = 12000;
setTimeout(foo, delay);
}
To solve this problem , I run the function a first time after the page has loaded.
function foo(){ ... }
window.onload = function() {
foo();
};
window.setInterval(function()
{
foo();
}, 5000);
This example builds on #Alnitak's answer, but uses await Promise for finer granularity of control within the loop cycle.
Compare examples:
let stillGoing = true;
(function foo() {
console.log('The quick brown fox did its thing');
if (stillGoing) setTimeout(foo, 5000);
})();
foo();
In the above example we call foo() and then it calls itself every 5 seconds.
But if, at some point in the future, we set stillGoing to false in order to stop the loop, we'll still get an extra log line even after we've issued the stop order. This is because at any given time, before we set stillGoing to false the current iteration will have already created a timeout to call the next iteration.
If we instead use await Promise as the delay mechanism then we have an opportunity to stop the loop before calling the next iteration:
let stillGoing = true;
(async function foo() {
console.log('The quick brown fox did its thing');
await new Promise(resolve => setTimeout(resolve, 5000));
if (stillGoing) foo();
})();
foo();
In the second example we start by setting a 5000ms delay, after which we check the stillGoing value and decide whether calling another recursion is appropriate.
So if we set stillGoing to false at any point, there won't be that one extra log line printed after we set the value.
The caveat is this requires the function to be async, which may or may not be an option for a given use.
For Those using React, here is how I solve this problem:
const intervalRef = useRef(0);
useEffect(() => {
if (condition is true){
if (intervalRef.current === 0) {
callMyFunction();
}
const interval = setInterval(() => {
callMyFunction();
}, 5_000);
intervalRef.current = interval;
} else {
clearInterval(intervalRef.current);
}
}, [deps]);
// YCombinator
function anonymous(fnc) {
return function() {
fnc.apply(fnc, arguments);
return fnc;
}
}
// Invoking the first time:
setInterval(anonymous(function() {
console.log("bar");
})(), 4000);
// Not invoking the first time:
setInterval(anonymous(function() {
console.log("foo");
}), 4000);
// Or simple:
setInterval(function() {
console.log("baz");
}, 4000);
Ok this is so complex, so, let me put it more simple:
function hello(status ) {
console.log('world', ++status.count);
return status;
}
setInterval(hello, 5 * 1000, hello({ count: 0 }));
If you can use RxJS, there is something called timer():
import { Subscription, timer } from 'rxjs';
const INITIAL_DELAY = 1;
const INTERVAL_DELAY = 10000;
const timerSubscription = timer(INITIAL_DELAY, INTERVAL_DELAY)
.subscribe(() => {
this.updateSomething();
});
// when destroying
timerSubscription.unsubscribe();
With ES2017, it may be preferable to avoid setInterval altogether.
The following solution has a much cleaner execution flow, prevents issues if the function takes longer than the desired time to complete, and allows for asynchronous operations.
const timeout = (delayMs) => new Promise((res, _rej) => setTimeout(res, delayMs));
const DELAY = 1_000;
(async () => {
while (true) {
let start_time = Date.now();
// insert code here...
let end_time = Date.now();
await timeout(DELAY - (end_time - start_time));
}
})();
There's a problem with immediate asynchronous call of your function, because standard setTimeout/setInterval has a minimal timeout about several milliseconds even if you directly set it to 0. It caused by a browser specific work.
An example of code with a REAL zero delay wich works in Chrome, Safari, Opera
function setZeroTimeout(callback) {
var channel = new MessageChannel();
channel.port1.onmessage = callback;
channel.port2.postMessage('');
}
You can find more information here
And after the first manual call you can create an interval with your function.
actually the quickest is to do
interval = setInterval(myFunction(),45000)
this will call myfunction, and then will do it agaian every 45 seconds which is different than doing
interval = setInterval(myfunction, 45000)
which won't call it, but schedule it only

Categories