RXJS countdown timer stops suddenly and i am able find out it is canceled by switchMap when trustworthy data is not emitted.
How to fix it? I need to start the timer by clicking the button.
Please find the jsbin of my example
const start = Rx.Observable.fromEvent(document.querySelector('#btn'), 'click');
start
.switchMap(() => {
return Rx.Observable.timer(0, 1000)
.takeUntil(Rx.Observable.timer(60000 + 1000))
})
.map((value) => 60000 - value * 1000)
.map(function(i) {
return 'Timer (second): ' + i / 1000;
}).subscribe(function(text) {
var container = document.querySelector('#app');
container.textContent = text;
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>RxJS 5 Operators</title>
<script src="https://npmcdn.com/#reactivex/rxjs#5.0.0-beta.3/dist/global/Rx.umd.js"></script>
</head>
<body>
<button id='btn'> start </button>
<p id='app'></p>
</body>
</html>
Try to change takeUntil(...) to take(...)
from:
.takeUntil(Rx.Observable.timer(60000 + 1000))
to:
.take(60000 / 1000 + 1)
Guessing reason:
If timer has accumulated temporal error, takeUntil could be fired before expected timer event occured.
time
[0] 0.000
[1] 1.103
...
[59] 60.234
61.003 <- takeUntil fired
[60] 61.123 <- expected last event lost
const start$ = Rx.Observable.fromEvent(document.querySelector('#btn'), 'click');
start$
.switchMap(()=> {
return Rx.Observable.timer(0, 1000)
.takeUntil(Rx.Observable.timer(3000 + 1000))
.map(value => 3000 - value * 1000)
.map(function (i) {
return 'Timer (second): ' + i/1000;
});
})
.subscribe(function (text) {
var container = document.querySelector('#app');
container.textContent = text;
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>RxJS 5 Operators</title>
<script src="https://npmcdn.com/#reactivex/rxjs#5.0.0-beta.3/dist/global/Rx.umd.js"></script>
</head>
<body>
<button id='btn'> start </button>
<p id='app'></p>
</body>
</html>
Related
I try to use a modified socket.io adaption for iobroker (smart home broker) with a setInterval function for showing every second the time ago of the last received timestamp of
It works so far, till the second updated timestamp comes... It seems clearInterval did not stop the interval and the timer is running twice with the old and new timestamp.
How and where do i have to clearInterval correctly?
Updated:
If onUpdate activates the servConn.getStates([stromvIDts]function and received a new timestamp of stromvIDts, the interval should run and show the time ago (every second updated). If (after around 2-3 min) a new value is there, the interval should stop and a new one starting and showing every second the past time of the new timestamp, e.g. "some seconds ago"... 2 Minutes ago ...)
My code:
servConn.namespace = 'mobile.0';
servConn._useStorage = false;
var stromv = 'hm-rpc.1.MEQ123456.1.POWER';
var subscriptions = [stromv];
var states = [];
servConn.init({
name: 'mobile.0',
connLink: 'http://IP:8082',
socketSession: ''
}, {
onConnChange: function(isConnected) {
if (isConnected) {
console.log('connected');
} else {
console.log('disconnected');
}
},
onUpdate: function(id, state) {
setTimeout(function() {
states[id] = state;
let stromvID = states[stromv].val;
let stromvIDts = states[stromv].ts;
//Get States of subsribed states if changed
servConn.getStates([stromvID], (error, states) => {
document.getElementById("stromvAktuell").innerHTML = stromvID + " W/h";
});
servConn.getStates([stromvIDts], (error, states) => {
function stopInterval() {
clearInterval(timerId);
};
timerId = setInterval(function() {
updateTimeAgo();
}, 1000);
function updateTimeAgo() {
let duration = moment(stromvIDts).fromNow();
console.log(duration);
document.getElementById("stromvTs").innerHTML = duration;
};
});
}, 0);
},
onError: function(err) {
window.alert(_('Cannot execute %s for %s, because of insufficient permissions', err.command, err.arg), _('Insufficient permissions'), 'alert', 600);
}
}, false, false);
servConn._socket.emit('subscribe', subscriptions);
<!DOCTYPE html>
<html lang="de">
<head>
<link rel="icon" href="data:,">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>TEST</title>
<script src="http://IP:8082/socket.io/socket.io.js"></script>
<script src="../tmp/conn.js"></script>
<script src="moment-with-locales.js"></script>
<script>
moment.locale('de')
</script>
</head>
<body>
<div>
Value: <b id="stromvAktuell">unknown</b>
<br> Last update: <b id="stromvTs">unknown</b>
<br>
</div>
</body>
Instead of creating new intervals and clearing the old one, just start one interval and update the value every time onUpdate runs:
If you want the time since the last update, you can use Date.now().
servConn.namespace = 'mobile.0';
servConn._useStorage = false;
var stromv = 'hm-rpc.1.MEQ123456.1.POWER';
var subscriptions = [stromv];
var states = [];
let lastTimestamp = -1;
let duration = -1;
timerId = setInterval(function() {
updateTimeAgo();
}, 1000);
function updateTimeAgo() {
console.log(Date.now() - lastTimeStamp);
document.getElementById("stromvTs").innerHTML = Integer((Date.now() - lastTimeStamp)/1000);
};
servConn.init({
name: 'mobile.0',
connLink: 'http://IP:8082',
socketSession: ''
}, {
onConnChange: function(isConnected) {
if (isConnected) {
console.log('connected');
lastTimestamp = Date.now();
} else {
console.log('disconnected');
}
},
onUpdate: function(id, state) {
setTimeout(function() {
states[id] = state;
let stromvID = states[stromv].val;
let stromvIDts = states[stromv].ts;
//Get States of subsribed states if changed
servConn.getStates([stromvID], (error, states) => {
document.getElementById("stromvAktuell").innerHTML = stromvID + " W/h";
});
lastTimeStamp = Date.now();
}, 0);
},
onError: function(err) {
window.alert(_('Cannot execute %s for %s, because of insufficient permissions', err.command, err.arg), _('Insufficient permissions'), 'alert', 600);
}
}, false, false);
servConn._socket.emit('subscribe', subscriptions);
<!DOCTYPE html>
<html lang="de">
<head>
<link rel="icon" href="data:,">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>TEST</title>
<script src="http://IP:8082/socket.io/socket.io.js"></script>
<script src="../tmp/conn.js"></script>
<script src="moment-with-locales.js"></script>
<script>
moment.locale('de')
</script>
</head>
<body>
<div>
Value: <b id="stromvAktuell">unknown</b>
<br> Last update: <b id="stromvTs">unknown</b>
<br>
</div>
</body>
d3.interval takes two parameters, callback and delay,e.g.
d3.interval(callback, delay).
I was wondering if it is possible to pass on a dynamic delay for each interval.
For example, in the following, I am asking the interval to run at 1000ms delay. But is there a way I can ask d3.interval to run at 0ms, 1000ms, 2000ms, 3000ms respectively for interval# 1,2,3,4.
I tried like desiredDelay[counterF] but it did not work.
const masterSelection = d3.selectAll('[class^="text"]');
const node = masterSelection.nodes();
const len = node.length - 1;
let counterF = 0;
const del = 1000;
const desiredDelay = [0, 1000, 2000, 3000]
let ticker = d3.interval(
e => {
const element = masterSelection['_groups'][0][counterF];
const selection = d3.select(element).node();
console.log(selection);
counterF++;
(counterF > len) ? ticker.stop(): null
}, del
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<div class='text1'>one</div>
<div class='text2'>two</div>
<div class='text3'>three</div>
<div class='text4'>four</div>
</body>
<script type="text/javascript" src="prod.js"></script>
</html>
Short answer: you can't.
If you look at the source code you'll see that if the delay is not null...
if (delay == null) return t.restart(callback, delay, time), t;
...it will be coerced to a number using the unary plus operator:
t.restart = function(callback, delay, time) {
delay = +delay,
etc...
What you can do is creating your own interval function, which is out of the scope of this answer.
Adapted from this, the following works as desired and is to be used with d3.timeout.
const masterSelection = d3.selectAll('[class^="text"]');
const node = masterSelection.nodes();
const len = node.length - 1;
let counter = 0;
//const del = 1000;
const delay = [0, 1000, 2000, 3000];
function show() {
const element = masterSelection["_groups"][0][counter];
const selection = d3.select(element).node();
console.log(selection);
counter++;
if (counter > len) return
d3.timeout(show, delay[counter]);
}
show();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<div class='text1'>one</div>
<div class='text2'>two</div>
<div class='text3'>three</div>
<div class='text4'>four</div>
</body>
<script type="text/javascript"></script>
</html>
I have the following javascript code:
let gemStones = [
"Amethyst",
"Diamond",
"Emerald",
"Ruby",
"Sapphire",
"Topaz",
"Onyx",
];
let randomGemStone = gemStones[Math.floor(Math.random()*gemStones.length)];
function findGems()
{
console.log("You found a " + randomGemStone + "!");
}
Here's the html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css.css">
<title>1. Gem Pond</title>
</head>
<body>
<h1>Gem Pond</h1>
<script src="main.js"></script>
<button id="gemPond_btn" onclick="findGems()">GET GEM</button>
</body>
</html>
When I click the "GET GEM" button several times in a row, I always get the same result instead of getting a random one.
I'm not sure what I'm doing wrong here.
Thanks in advance.
Move the let randomGemStone line into the findGems function:
function findGems()
{
let randomGemStone = gemStones[Math.floor(Math.random()*gemStones.length)];
console.log("You found a " + randomGemStone + "!");
}
Otherwise you run it only once on page load and not every time you click the button.
let gemStones = [
"Amethyst",
"Diamond",
"Emerald",
"Ruby",
"Sapphire",
"Topaz",
"Onyx",
];
const findGems = () => {
let randomGemStone = gemStones[Math.floor(Math.random()*gemStones.length)];
console.log(`You found a ${randomGemStone}!`);
}
Note I have moved randomGemStone inside the function. Or the value will only be updated once when the script loads, this way it will be random everytime findGems() is called
For my program the user inputs a number to see if this matches the computer guess. To make things simple and I can better see the flow of the code the number to be guesses is between 0-1.
Issue I'm having is that I'm not properly saving the data to localStorage and incrementing the key value, if I manually add a value to my key localStorage only updates correctly when I refresh the page, each time I make a guess the program seems to respond correctly but have to refresh after the guess. Any feedback would be appreciated.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Guess # | Main</title>
</head>
<body>
<h1>Guess Number Between 0-1</h1>
<form>
<input id="enter-value" placeholder="Enter a number"></input>
<button id="submit-number">Submit</button>
<button id="reset-button">Reset</button>
</form>
<div id="result-section"></div>
<div id="count-section"></div>
<script src="functions.js"></script>
<script src="dom.js"></script>
</body>
</html>
dom.js
const submitNumber= document.getElementById('submit-number')
const enterValue= document.getElementById('enter-value')
const resultSection= document.getElementById('result-section')
const countSection= document.getElementById('count-section')
const resetButton= document.getElementById('reset-button')
submitNumber.addEventListener('click', function(e){
e.preventDefault()
resultSection.textContent=''
countSection.textContent=''
const userNumInput= enterValue.value.trim()
userGuess(userNumInput)
})
resetButton.addEventListener('click', function(e){
e.preventDefault()
localStorage.clear()
countSection.textContent=''
resetField()
resultSection.textContent=''
})
functions.js
let userTallyTotal
let computerTallyTotal
const userGuess= (userNumber)=>{
let message=''
localStorage.getItem('userWinsTotal')!= null ? userTallyTotal : userTallyTotal= 0
localStorage.getItem('computerWinsTotal')!= null ? computerTallyTotal : computerTallyTotal= 0
//Current problem I'm facing now is that refreshing the page and submitting a value changes localStorage value to NaN
let numberChoice= userNumber
const randomNum= Math.floor(Math.random()*1)
numberChoice= parseInt(numberChoice, 10)
if (isNaN(numberChoice)){
message+= 'This is not a number, try again.'
displayResult(message)
resetField()
return
}
if (numberChoice===randomNum) {
parseInt(userTallyTotal, 10)
userTallyTotal++
localStorage.setItem('userWinsTotal', userTallyTotal)
message+= `You won! You guessed: ${numberChoice} and the computer guessed: ${randomNum}`
} else {
parseInt(computerTallyTotal, 10)
computerTallyTotal++
localStorage.setItem('computerWinsTotal', computerTallyTotal)
message+= `Sorry Try Again! You guessed: ${numberChoice} and the computer guessed: ${randomNum}`
}
resetField()
displayResult(message)
countWins(userTallyTotal,computerTallyTotal)
}
const displayResult= (message)=>{
const displayResultEl= document.createElement('h2')
displayResultEl.textContent= message
document.getElementById('result-section').appendChild(displayResultEl)
}
const countWins= (userTallyTotal, computerTallyTotal)=>{
const countWinsEl= document.createElement('h2')
countWinsEl.textContent= `User Total Win: ${userTallyTotal} Computer Total Win: ${computerTallyTotal}`
document.getElementById('count-section').appendChild(countWinsEl)
}
const resetField= ()=>{
enterValue.value=''
enterValue.focus()
}
I believe that localStorage can only hold strings, so userWinsTotal will be converted to string. Could you try to Math.parseInt it before the increment?
I found all problems:
1.localStorage , as mentioned by #Yesset Zhussupov, can store only strings so on reload you need to parse your consts:
const userTallyTotal= parseInt(localStorage.getItem('userWinsTotal'))
const computerTallyTotal= parseInt(localStorage.getItem('computerWinsTotal'))
You calling userGuess every time and you reseting your counters with:
userWinsTotal= userTallyTotal
computerWinsTotal= computerTallyTotal
where userTallyTotal and computerTallyTotal are const-type.
Fix for that is simply:
userWinsTotal= userWinsTotal||userTallyTotal
computerWinsTotal= computerWinsTotal||computerTallyTotal
Which will check if that variables are initialized or not
This is my final code, tested and I no longer have the issue when refreshing the page. Appreciate the help and any final comments that could help me improve this as a beginner.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Guess # | Main</title>
</head>
<body>
<h1>Guess Number Between 0-1</h1>
<form>
<input id="enter-value" placeholder="Enter a number"></input>
<button id="submit-number">Submit</button>
<button id="reset-button">Reset</button>
</form>
<div id="result-section"></div>
<div id="count-section"></div>
<script src="functions.js"></script>
<script src="dom.js"></script>
</body>
</html>
dom.js
const submitNumber= document.getElementById('submit-number')
const enterValue= document.getElementById('enter-value')
const resultSection= document.getElementById('result-section')
const countSection= document.getElementById('count-section')
const resetButton= document.getElementById('reset-button')
submitNumber.addEventListener('click', function(e){
e.preventDefault()
resultSection.textContent=''
countSection.textContent=''
const userNumInput= enterValue.value.trim()
userGuess(userNumInput)
})
resetButton.addEventListener('click', function(e){
e.preventDefault()
localStorage.clear()
countSection.textContent=''
resultSection.textContent=''
resetField()
countWins(0,0)
})
functions.js
let userTallyTotal= localStorage.getItem('userWinsTotal')
let computerTallyTotal= localStorage.getItem('computerWinsTotal')
const checkStoredData=()=>{
localStorage.getItem('userWinsTotal')!= null ? userTallyTotal : userTallyTotal= 0
localStorage.getItem('computerWinsTotal')!= null ? computerTallyTotal : computerTallyTotal= 0
}
const userGuess= (userNumber)=>{
checkStoredData()
let message=''
let numberChoice= userNumber
const randomNum= Math.floor(Math.random()*1)
numberChoice= parseInt(numberChoice, 10)
if (isNaN(numberChoice)){
message+= 'This is not a number, try again.'
displayResult(message)
resetField()
return
}
if (numberChoice===randomNum) {
parseInt(userTallyTotal, 10)
userTallyTotal++
localStorage.setItem('userWinsTotal', userTallyTotal)
message+= `You won! You guessed: ${numberChoice} and the computer guessed: ${randomNum}`
} else {
parseInt(computerTallyTotal, 10)
computerTallyTotal++
localStorage.setItem('computerWinsTotal', computerTallyTotal)
message+= `Sorry Try Again! You guessed: ${numberChoice} and the computer guessed: ${randomNum}`
}
resetField()
displayResult(message)
countWins(userTallyTotal,computerTallyTotal)
}
const displayResult= (message)=>{
const displayResultEl= document.createElement('h2')
displayResultEl.textContent= message
document.getElementById('result-section').appendChild(displayResultEl)
}
const countWins= (userTallyTotal, computerTallyTotal)=>{
const countWinsEl= document.createElement('h2')
countWinsEl.textContent= `User Total Win: ${userTallyTotal || 0} Computer Total Win: ${computerTallyTotal || 0}`
document.getElementById('count-section').appendChild(countWinsEl)
}
const resetField= ()=>{
enterValue.value=''
enterValue.focus()
countSection.textContent=''
}
countWins(userTallyTotal, computerTallyTotal)
So if you notice I have created a link in the preview below. What I'm looking for is if someone clicks on the link. I want to replace the text for a 60 seconds timer, and once the 60 seconds are finished, the text and link should reappear, and the same process continues.
Can someone help?
Request code again
You can use javascript setInterval and then clearInterval for this.
Your code while using jQuery should look like this: (change the value of timer_output_initial to time in number of seconds you want)
var display_timer_interval;
var timer_output_initial = 5
var timer_output = timer_output_initial;
var initial_text = "";
$("#timer_link").on("click",function(){
var clicked_element = $(this);
initial_text = clicked_element.html();
display_timer_interval = setInterval(function(){
display_time(clicked_element);
}, 1000);
});
function display_time(element){
timer_output = timer_output-1;
if(timer_output === 0) {
clearInterval(display_timer_interval);
timer_output = timer_output_initial;
element.html(initial_text);
}else{
$(element).html(timer_output);
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
</head>
<body>
Request code again
</body>
</html>