In the following piece of code I try to demonstrate how concatMap preserves the order of events, even if the action performed on events do not complete in order.
Now I get the error that
delayerObservable.complete() is not a function
This basically is just taken from a tutorial. I call next(), and then I call complete(). It should work, at least that's what I thought.
I can achive the desired functionality by returning randomDelayer.first()
return randomDelayer.first()
but I would like to complete the observable from within, since I might want to send out more events than just one.
const myTimer = Rx.Observable.create((observer) => {
let counter = 0;
setInterval( () => {
observer.next(counter++);
console.log('called next with counter: '+ counter);
},2000);
});
const myRandomDelayer = myTimer.concatMap( (value) => {
const randomDelayer = Rx.Observable.create( (delayerObservable) => {
const delay = Math.floor(Math.random()*1000);
setTimeout(() => {
console.log(delayerObservable);
delayerObservable.next('Hello, I am Number ' + value + ' and this was my delay: ' + delay);
delayerObservable.complete(); // <<-- this does not work (not a function)
}, delay);
});
return randomDelayer;
});
myRandomDelayer.subscribe( (message) => {
console.log(message);
});
It seems that there are quite a few changes between version 4 and 6 of the rxjs-framework. The working version of the defective source is this:
const { Observable } = rxjs;
const { map, filter, concatMap, pipe } = rxjs.operators;
console.log('Starting....');
const myTimer = Observable.create((observer) => {
let counter = 0;
setInterval( () => {
counter++;
if (counter < 10){
console.log('nexting now with counter ' + counter);
observer.next(counter);
} else {
observer.complete();
}
},1000);
});
const myRandomDelayer = myTimer.pipe(
concatMap( (value) => {
const randomDelayer = Observable.create( (delayerObservable) => {
const delay = Math.floor(Math.random()*5000);
setTimeout(() => {
delayerObservable.next('Hello, I am Number ' + value + ' and this was my delay: ' + delay);
delayerObservable.complete();
}, delay);
});
return randomDelayer;
})
);
myRandomDelayer.subscribe( (message) => {
console.log(message);
});
Related
First of all I want to know if I am right about cause of the problem.
const updateScore = (isCorrect) => {
// Update Game Variables
if (isCorrect === true) {
counter++;
score += 100;
}
};
// Reset Styling
const resetLoadedQuestionStyling = (isCorrect) => {
questionScreen.style.display = 'none';
answerArr.forEach(answer => {
answer.classList.remove('correct');
answer.classList.remove('wrong');
answer.classList.remove('disable');
});
updateScore(isCorrect);
};
const styleAnswer = (div, isCorrect) => {
if (isCorrect === true) {
div.classList.add('correct');
} else {
div.classList.add('wrong');
for (let i = 0; i < answerArr.length; i++) {
if (i === currentQuestion.correct) {
answerArr[i].classList.add('correct');
}
}
}
// Prevent Second Check
answerArr.forEach(answer => {
answer.classList.add('disable');
});
// Reset Styling
setTimeout(() => {
resetLoadedQuestionStyling(isCorrect);
}, 3000);
};
const checkAnswer = (div, index) => {
const userChoice = index;
// Default Answer State
let isCorrect = false;
if (userChoice === currentQuestion.correct) {
isCorrect = true;
}
styleAnswer(div, isCorrect);
};
answerArr.forEach((div, index) => {
div.addEventListener('click', () => {
checkAnswer(div, index);
});
});
My counter updates 1,time, that 2 times... and I think the cause of this issue is that my EventListener is in a forEach loop, is that right?
How to prevent it?
Thanks!
EDIT: Addded more of the code in order to get my idea better.
EDIT: answerArr is array of 4 divs in my HTML
There may be a setTimeout-related issue. Every time an answer is clicked, the counter is set to be incremented after 3 seconds.
Here's the sequence when an answer is clicked:
'click'
checkAnswer ->
styleAnswer ->
setTimeout =>
resetLoadedQuestionStyling ->
updateScore ->
counter++
Below is the code with all of the unrelated lines removed. It does increment the counter after every click, but only after 3 seconds.
const answerArr = [...document.querySelectorAll('button')];
let counter = 0;
const span = document.getElementById('counter');
const updateScore = (isCorrect) => {
if (isCorrect === true) {
counter++
}
span.innerText = counter;
}
const resetLoadedQuestionStyling = (isCorrect) => {
updateScore(isCorrect)
}
const styleAnswer = (div, isCorrect) => {
// Reset Styling after 3 seconds
setTimeout(() => {
resetLoadedQuestionStyling(isCorrect);
}, 3000);
}
const checkAnswer = (div, index) => {
styleAnswer(div, true);
}
answerArr.forEach((div, index) => {
div.addEventListener('click', () => {
checkAnswer(div, index);
});
});
<button>Answer 1</button><br>
<button>Answer 2</button><br>
<button>Answer 3</button><br>
<button>Answer 4</button><br>
<p>Counter: <span id="counter"></span></p>
I am trying to access to intervalId "useState" variable inside of a timeInterval function. But the scope is not working properly. intervalId is always null inside ot the timeInterval function, that means that it does not about delay of value assignation.
export function useLocalCommands(length, id) {
const [intervalId, setIntervalId] = useState(null)
const [currentIndex, setCurrentIndex] = useState(10)
const [timeInterval, setTimeInterval] = useState(Constants.READING.READING_TIME_INTERVAL)
let pauseReading = () => {
console.log("pause:" + intervalId)
clearInterval(intervalId)
}
let startReading = () => {
console.log("play")
pauseReading()
if (currentIndex < length) {
setIntervalId(setInterval(() => {
setCurrentIndex((index) => {
if (index < length) {
if (id && index % 40 === 0) {
Meteor.call('myBooks.updateIndex', id, index, (err, res) => {
// show a toast if err
})
}
return index + 1
} else {
console.log("pauseReading: " + intervalId)
pauseReading();
}
})
}, timeInterval))
}
}
}
Thanks you,
Best.
IntervalId is being used from closure which is why when the setInterval runs, the values being takes at the time of declaration. However setIntervalId triggeres a state update and even though the value of state is updated, your timerId within setInterval function continues to point to the old state that it used from closure.
Instead of using state, you can make use of useRef to store the timerId. Since refs are mutated, they aren't affected by closure
export function useLocalCommands(length, id) {
const intervalId = useRef(null)
const [currentIndex, setCurrentIndex] = useState(10)
const [timeInterval, setTimeInterval] = useState(Constants.READING.READING_TIME_INTERVAL)
let pauseReading = () => {
console.log("pause:" + intervalId.current)
clearInterval(intervalId.current)
}
let startReading = () => {
console.log("play")
pauseReading()
if (currentIndex < length) {
intervalId.current = setInterval(() => {
setCurrentIndex((index) => {
if (index < length) {
if (id && index % 40 === 0) {
Meteor.call('myBooks.updateIndex', id, index, (err, res) => {
// show a toast if err
})
}
return index + 1
} else {
console.log("pauseReading: " + intervalId.current)
pauseReading();
}
})
}, timeInterval);
}
}
}
I am refactoring some code that crawls some web pages (removing "callback hell"), and want a three second delay between each request. Here is the request function:
const getHTML = function(page, i) {
return new Promise(function(resolve, reject) {
setTimeout(function () {
api.makeAPIGetRequest(page).then((html) => {
resolve(html);
}).catch((err) => {
reject(err);
})
}, i * 3000);
});
}
I am traversing an array of objects then an array:
let p = [
{
location: 'England',
pages: [1, 3, 5]
},
{
location: 'Scotland',
pages: [2, 4, 6]
}
];
The problem is is that the output is random (because of the delay):
Page 1 - Loaded
Page 2 - Loaded
Page 5 - Loaded
Page 4 - Loaded
Page 3 - Loaded
Page 6 - Loaded
It should be:
Page 1 - Loaded
Page 3 - Loaded
Page 5 - Loaded
Page 2 - Loaded
Page 4 - Loaded
Page 6 - Loaded
Here is my code:
p.map(async (data) => {
await crawlLocationPages(data);
})
function crawlLocationPages(data) {
return Promise.all(
data.pages.map(async (page, i) => {
await getHTML(page, i).then((html) => { // <-- waits 3 seconds
console.log('Page ' + page + ' - Loaded' );
});
})
).then(() => {
})
};
I would rather keep the object and array model as it is.
Any help is appreciated.
await doesn't work inside .map and .forEach, but it does work inside for loops. And of course, it has to be inside an async function.
const run = async () => {
for(let data of p){
await crawlLocationPages(data);
}
}
const crawlLocationPages = async data => {
for(let page of data.pages){
const html = await getHTML(page);
console.log('Page ' + page + ' - Loaded - HTML = ', html );
await pause();
}
}
const pause = () => new Promise( (resolve, reject) => setTimeout(resolve, 3000) );
run()
Solved it using ES6 generators and yield.
function* crawlGenerator() {
for (let i = 0; i <= (p.length - 1); i++) {
yield crawlLocationPages(p[i]);
}
}
let crawl = crawlGenerator();
crawl.next();
function crawlLocationPages(data) {
return Promise.all(
data.pages.map(async (page, i) => {
await getHTML(page, i).then((html) => { // <-- waits 3 seconds
console.log('Page ' + page + ' - Loaded' );
});
})
).then(() => {
crawl.next();
})
};
More information here: https://davidwalsh.name/async-generators
This approach might be less confusing but only works if each request take no longer than 3 seconds.
pages = p.flatMap(location => location.pages);
page = 0;
var interval = setInterval(() => {
if(page === pages.length){
clearInterval(interval);
}
api.makeAPIGetRequest(pages[page++]).then((html) => {
console.log('Page ' + page + ' - Loaded' );
}).catch((err) => {
console.error(err);
});
}, 3000);
Or call next inside then
function getPage(pages, i) {
const ts = Date.now();
api.makeAPIGetRequest(pages[i++]).then((res)=>{
console.log(res);
if(i < pages.length) {
const delay = Math.max(3000 - (Date.now() - ts), 0);
setTimeout(getPage(pages, i), delay);
}
})
}
pages = p.flatMap(location => location.pages);
getPages(pages, 0);
I´m trying to execute an http get request to an api every 5 seconds from my reactjs app, although using the setTimeout function, after the first 5 seconds all the requests are done.
getPerson = (url) => {
axios.get(url)
.then(response => {
let person = {};
person.id = response.data.id;
person.name = response.data.name;
person.type = response.data.type;
this.state.persons.push(person);
this.setState({ persons: this.state.persons });
});
}
componentDidMount() {
for(var i = 1; i < 11; i++){
this.getPerson(this.apiUrl + i);
setTimeout(function () {
console.log("waiting for the next call.");
}, 5000);
}
}
componentDidMount() {
let i = 0;
let interval = setInterval(() => {
if (i<11) {
this.getPerson(this.apiUrl + i);
i++;
console.log("waiting for the next call.");
}
else {
clearInterval(interval)
}
}, 5000);
}
You should use setInterval() for that purpose, not setTimeout()
EDIT: In case you somehow don't have access to this in setInterval(), even though you should because it's an arrow function, you can use a workaround
let that = this and then call your method with that.getPerson(that.apiUrl + i);
Still having the problem to access "this" object from inside anonymous block function.
getPerson = (url) => {
axios.get(url)
.then(response => {
let person = {};
person.id = response.data.id;
person.name = response.data.name;
this.state.loadedPersons.push(person);
this.setState({ loadedPersons: this.state.loadedPersons });
});}
componentDidMount() {
for (var j = 1; j < 152; j++) {
this.getPerson(this.apiUrl + j);
}
let i = 1;
let that = this.state;
console.log("That out of in interval:" + that);
let interval = setInterval(function (that) {
console.log("That in interval:" + that);
if ((i<152))
that.persons.push(that.loadedPersons[i]);
i++;
}
else {
clearInterval(interval)
}
}, 5000);
}
Console output:
That out of interval: [Object object]
That in interval: undefined
I am trying to implement two classes that can deal with asynchronous tasks in JavaScript:
Class Task: mimics the execution of a task with setTimeout. Once the timer expires, the task is considered completed.
Class TaskManager: has a capacity parameter to limit the numbers of tasks that can be executing in parallel.
I thought if I could just call the loop function recursively, just to keep checking if one job is done, I could proceed to the next job. But this leads immediately to a "Maximum call stack size exceeded" error.
Can someone explain how I can fix this?
class Task {
constructor(time) {
this.time = time;
this.running = 0;
}
run(limit, jobs, index) {
setTimeout(() => {
console.log('hello', index);
this.done(limit, jobs, index);
}, this.time);
}
done(limit, jobs, index) {
jobs.splice(index, 1);
console.log(jobs);
}
}
class TaskManager {
constructor(capacity) {
this.capacity = capacity;
this.jobs = [];
this.index = 0;
this.running = 0;
this.pending = [];
}
push(tk) {
this.jobs.push(tk);
this.index += 1;
const loop = () => {
if (this.jobs.length === 0) {
return;
}
if (this.jobs.length <= this.capacity) {
this.running += 1;
tk.run(this.capacity, this.jobs, this.index-1);
return;
}
loop();
}
loop();
}
}
const task = new Task(100);
const task1 = new Task(200);
const task2 = new Task(400);
const task3 = new Task(5000);
const task4 = new Task(6000);
const manager = new TaskManager(3);
manager.push(task);
manager.push(task1);
manager.push(task2);
manager.push(task3);
manager.push(task4);
You should not implement the busy loop, as that will block the event loop and so no user UI events or setTimeout events will be processed.
Instead respond to asynchronous events.
If you let the setTimeout callback resolve a Promise, it is not so hard to do.
I modified your script quite drastically. Here is the result:
class Task {
constructor(id, time) {
this.id = id;
this.time = time;
}
run() {
console.log(this + ' launched.');
return new Promise(resolve => {
setTimeout(() => {
console.log(this + ' completed.');
resolve();
}, this.time);
});
}
toString() {
return `Task ${this.id}[${this.time}ms]`;
}
}
class TaskManager {
constructor(capacity) {
this.capacity = capacity;
this.waiting = [];
this.running = [];
}
push(tk) {
this.waiting.push(tk);
if (this.running.length < this.capacity) {
this.next();
} else {
console.log(tk + ' put on hold.');
}
}
next() {
const task = this.waiting.shift();
if (!task) {
if (!this.running.length) {
console.log("All done.");
}
return; // No new tasks
}
this.running.push(task);
const runningTask = task.run();
console.log("Currently running: " + this.running);
runningTask.then(() => {
this.running = this.running.filter(t => t !== task);
console.log("Currently running: " + this.running);
this.next();
});
}
}
const a = new Task('A', 100);
const b = new Task('B', 200);
const c = new Task('C', 400);
const d = new Task('D', 5000);
const e = new Task('E', 6000);
const manager = new TaskManager(3);
manager.push(a);
manager.push(b);
manager.push(c);
manager.push(d);
manager.push(e);