I have a class with multiple methods, each method either call async functions, or manipulating data created by other methods.
How can I make sure that calling class methods would wait for previous method finish before proceeding?
Here is a simple example that calls random methods of the class, each method takes random number of seconds to execute, I need to make sure that each called method waited for result of previous method:
class Test
{
constructor()
{
//ignore the constructor, it's simplified for this example and does not play any role in the question
const list = [...Array(~~(Math.random()*3)+3)].map(e=>~~(Math.random()*3+1));
console.log("expected: " + list);
list.forEach(f => this["method_" + f]());
}
method_1()
{
return new Promise(resolve => setTimeout(() =>
{
console.log(1);
resolve("res 1");
}, Math.random() * 1000 + 100));
}
method_2()
{
return new Promise(resolve => setTimeout(() =>
{
console.log(2);
resolve("res 2");
}, Math.random() * 1000 + 100));
}
method_3()
{
func3();
}
}
new Test();
function func3()
{
new Promise(resolve => setTimeout(() =>
{
console.log(3);
resolve("res 3");
}, Math.random() * 1000 + 100));
}
I can't use then method, because each method doesn't know which method was executed before it...or can I?
I was thinking about adding a sort of queue, where called method would be placed when called, and executed when previous promise was resolved...not sure if that would be a best approach.
Since essentially I needed make this class synchronous, using queue suggested by #Bergi works well:
class Test
{
constructor()
{
this.queueList = Promise.resolve();
//ignore the rest of the constructor, it's simplified for this example and does not play any role in the question
const list = [...Array(~~(Math.random()*3)+3)].map(e=>~~(Math.random()*3+1));
console.log("expected: " + list);
list.forEach(f => this["method_" + f]());
}
queue(func)
{
return (this.queueList = this.queueList.then(func).catch(console.error));
}
method_1()
{
return this.queue(() => new Promise(resolve => setTimeout(() =>
{
console.log(1);
resolve("res 1");
}, Math.random() * 1000 + 100)));
}
method_2()
{
return this.queue(() => new Promise(resolve => setTimeout(() =>
{
console.log(2);
resolve("res 2");
}, Math.random() * 1000 + 100)));
}
method_3()
{
return this.queue(func3);
}
}
new Test();
function func3()
{
return new Promise(resolve => setTimeout(() =>
{
console.log(3);
resolve("res 3");
}, Math.random() * 1000 + 100));
}
Related
I feel stupid because I do not find what I want to do...
It is in PURE Javascript.
I wan't to call a function, and stop it (or kill it, or whatever) next some seconds.
Here is my actual code :
function scrollThumbnails() {
const thumbnails = document.querySelectorAll('.thumbnail');
for (thumbnail of thumbnails) {
thumbnail.classList.add('active');
await verticalSlider(thumbnail);
resetSliderPosition(thumbnail);
thumbnail.classList.remove('active');
}
scrollThumbnails();
}
async function verticalSlider(element) {
const slider = element.querySelector('.vertical-carrousel');
const max = slider.offsetHeight - slider.parentElement.offsetHeight;
var toTop = 0;
while (toTop > -max) {
await new Promise((resolve) => setTimeout(resolve, 50));
toTop--;
slider.style.top = toTop + 'px';
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
function resetSliderPosition(element) {
const slider = element.querySelector('.vertical-carrousel');
slider.style.removeProperty('top');
}
So here, i want to call 'verticalSlider' in my loop, but if it is over than 45 seconds, I want to stop it and go on the next thumbnail.
Is it possible ? Do I miss something ?
Thanks by advanced :)
This is simple example without async and await with manual timeout counter implementation.
// Function, which accepts time out value in seconds (counting from 0)
const myFunct = (timeOutSec) => {
// Get current time
const ct = Date.now();
// Set counter for example function logic
let secCounter = ct - 1000;
// Start eternal loop
while(true) {
// Get in loop time
const ilt = Date.now();
// Compare current time and in loop time
// If current time + timeout sec < in loop time, break it
if(ct + timeOutSec * 1000 < ilt) break;
// Here do main function logic in loop
// for example, console log each second
if(ilt - secCounter > 1000) {
console.log(`time ${ilt}`);
secCounter += 1000;
}
}
}
// Test running function for 4 sec
myFunct(3);
console.log(`Completed at ${Date.now()}`);
Or similar with promises
// Async timeout function
const timeout = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Promise function which accepts timout value in seconds (counting from 0)
const myPFunct = (timeOutSec) => {
return new Promise(async (resolve, reject) => {
// Current time
const ct = Date.now();
// start eternal loop
while(true) {
// In loop time
const ilt = Date.now();
// Check in-loop time, current time and timeout
if(ct + timeOutSec * 1000 < ilt) {
resolve(`completed at ${ilt}`);
break;
}
// Do something
console.log(`time: ${ilt}`);
// Wait 1 sec
await timeout(1000);
}
});
}
// Test async function
const test = async () => {
// Run myPFunct for 4 sec
const result = await myPFunct(3);
console.log(`Test function result: ${result}`);
}
// Run test function
test();
Thanks to #tarkh solution.
If someone try to do an horizontal slider like me, or something with loop, I have to modify the code (because with the source solution, the resolve goes up to all the async methods).
Here is the final solution (I have to name the loop to break it and resolve the current promise - there are maybe simplest solution ?) :
window.onload = function () {
scrollThumbnails();
}
const scrollThumbnails = async () => {
const thumbnails = document.querySelectorAll('.thumbnail');
for (thumbnail of thumbnails) {
thumbnail.classList.add('active');
await verticalSliderWithTimeout(thumbnail, 45000);
thumbnail.classList.remove('active');
}
scrollThumbnails();
}
const timeout = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const verticalSliderWithTimeout = (element, timeoutDelay) => {
return new Promise(async (resolve, reject) => {
const currentTime = Date.now();
const slider = element.querySelector('.vertical-carrousel');
const max = slider.offsetHeight - slider.parentElement.offsetHeight;
var toTop = 0;
slider.style.top = toTop + 'px';
sliderLoop: while (toTop > -max) {
const inLoopTime = Date.now();
if(currentTime + timeoutDelay < inLoopTime) {
break sliderLoop;
}
await timeout(50);
toTop--;
slider.style.top = toTop + 'px';
}
resolve();
});
}
HTML code if needed (Handlebars) :
<div class="thumbnail">
<div class="photos-slider">
<div id="vertical-carrousel" class="vertical-carrousel">
{{#each this.photos}}
<img src="/image/{{../this.agency.product_type}}/{{../this.agency.id}}/{{../this.reference}}/{{urlencode this}}"
onerror="{{this}}" />
{{/each}}
</div>
</div>
</div>
I am looking for an optimal solution for the described problem.
Scenario: There is a function getData() which is being called every second.
If it is called now I want to ignore any call to this function for let say 5 sec.
how best we can achieve this in javascript.
Save last call time and check if passed more than 5 seconds:
var lastCall = 0;
function getData() {
if (lastCall >= moment().subtract(5, 'mins').unix()) {
return;
}
lastCall = moment().unix();
/* rest of code */
}
Add a flag and toggle it after 5 seconds and on each not ignored call:
var shouldIgnore = false;
function getData() {
if (shouldIgnore) {
return;
}
shouldIgnore = true;
setTimeout(() => {
shouldIgnore = false;
}, 5000);
/* rest of code */
}
There are may may using setTimeout you can do. I have give sample example with some util mathod to make it simpler.
Throttle Function:
const throttle = (fn, ms = 0) => {
let lastRunTime;
return function(...args) {
const currTime = +new Date();
if (!lastRunTime || currTime - lastRunTime > ms) {
lastRunTime = +new Date();
fn.apply(this, args);
}
};
};
How to use it:
(async function throttleEx() {
const logTill1Sec = throttle(log, 1 * 1000);
logTill1Sec("deepakt_1");
await new Promise(r => setTimeout(r, 500)); //2 sec virtual delay
logTill1Sec("deepak_t2");
})();
Output:
Mr. deepakt_1
Here you notice, even I call logAfter5Sec multiple times. It execute last one. You can write same way call once.
const throttle = (fn, ms = 0) => {
let lastRunTime;
return function(...args) {
const currTime = +new Date();
if (!lastRunTime || currTime - lastRunTime > ms) {
lastRunTime = +new Date();
fn.apply(this, args);
}
};
};
(async function throttleEx() {
const logTill1Sec = throttle(log, 1 * 1000);
logTill1Sec("deepakt_1");
await new Promise(r => setTimeout(r, 500)); //2 sec virtual delay
logTill1Sec("deepak_t2");
})();
const debounce = (fn, ms = 0) => {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
};
const dLog = debounce(log, 200); //ms time
dLog("deepak11");
dLog("deepak22");
dLog("deepak33");
function log(name) {
console.log(`Mr. ${name}`);
}
(async function() {
const logAfter5Sec = debounce(log, 1 * 1000);
logAfter5Sec("deepak");
await new Promise(r => setTimeout(r, 500)); //2 sec virtual delay
logAfter5Sec("deepak2");
})();
I am trying to return a Promise object ever 1000ms, but i am not sure how to access the data returned in the Promise when it is inside a setInterval() callback.
EDIT
I appears i was not being very clear as to my intentions, so i will try and explain what it is i am trying to do. I making a count down where by the necessary calculations are are don every 1000ms based on specified end date.
Here is the code that provides the return value i would like returned as a Pormise value every 1000ms:
calculateTimeRemaining(endDate: string) {
const { secondsInDay, daysOfYear, secondsInHour, secondsInMinute } = this.unitsOfTime;
let distance: number =
(Date.parse(new Date(endDate).toString()) - Date.parse(new Date().toString())) / this.increment;
if (distance > 0) {
// Years left
if (distance >= daysOfYear * secondsInDay) {
// 365.25 * 24 * 60 * 60
this.timeRemaining.years = Math.floor(distance / (daysOfYear * secondsInDay));
distance -= this.timeRemaining.years * daysOfYear * secondsInDay;
}
// Days left
if (distance >= secondsInDay) {
// 24 * 60 * 60
this.timeRemaining.days = Math.floor(distance / secondsInDay);
distance -= this.timeRemaining.days * secondsInDay;
}
// Hours left
if (distance >= secondsInHour) {
// 60 * 60
this.timeRemaining.hours = Math.floor(distance / secondsInHour);
distance -= this.timeRemaining.hours * secondsInHour;
}
// Minutes left
if (distance >= secondsInMinute) {
// 60
this.timeRemaining.minutes = Math.floor(distance / secondsInMinute);
distance -= this.timeRemaining.minutes * secondsInMinute;
}
// Seconds left
this.timeRemaining.seconds = distance;
}
return this.timeRemaining;
}
Example:
const interval = window.setInterval(() => {
return new Promise((resolve, reject) => {
resolve('Hello');
});
}, 1000);
How to i access the Promise object with .then() afterwards?
Does not work:
interval.then((data) => console.log(data);
What you are looking for is an Observable, not a Promise. With promises, the callback you pass to then is executed at most once, so this:
interval.then((data) => console.log(data));
...will never print "Hello" more than once, even if you corrected the following mistakes in your code:
Whatever you return in a setInterval callback function is ignored.
setInterval does not return a promise, but an integer number, uniquely identifying the interval timer that was created.
On the other hand, an Observable can emit multiple events, contrary to a Promise.
There is an Observable proposal for EcmaScript, but you could create your own -- very simplified -- version of it:
class Observable {
constructor(exec) {
this.listeners = new Set;
exec({
next: (value) => this.listeners.forEach(({next}) => next && next(value)),
error: (err) => this.listeners.forEach(({error}) => error && error(err)),
complete: () => this.listeners.forEach(({complete}) => complete && complete())
});
}
subscribe(listeners) {
this.listeners.add(listeners);
return { unsubscribe: () => this.listeners.delete(listeners) }
}
}
// Create an Observable instead of a Promise;
const interval = new Observable(({next}) => {
setInterval(() => next("Hello"), 1000);
});
// Subscribe to that Observable
const subscription = interval.subscribe({ next: (data) => console.log(data) });
// Optionally use the returned subscription object to stop listening:
document.querySelector("button").addEventListener("click", subscription.unsubscribe);
<button>Stop listening</button>
Note that several JavaScript frameworks have an implementation of Observable.
Depending on what you're actually trying to do, an async iterable might do the job.
The difference is that an async iterable will only generate the next promise if you consume the last one. Intervals in JavaScript are tricky, even without promises. They try to run their callback at regular intervals, but the execution of any callback may be delayed if the interpreter is busy. That delay will not propagate, though. Also, short intervals will be throttled for background tabs.
Assuming your code is always waiting to consume the async iterable (e.g. in a for…of loop), you could do this:
function delay(t) {
return new Promise(resolve => setTimeout(resolve, t))
}
async function *interval(t) {
while(true) {
let now = Date.now()
yield "hello"
await delay(now - Date.now() + t)
}
}
for await(const greeting of interval(1000)) console.log(greeting)
For interval you can define the interval function like this
function interval() {
return new Promise(function(resolve, reject) {
setInterval(function() {
resolve('Hello');
}, 1000)
})
};
For interval you can use this:
way 1:
interval().then((x) => {
console.log(x);
})
way 2:
const intervalId = setInterval(() => {
interval().then((x) => {
console.log(x);
}, 1000)
})
This is just for stop the interval function after some time.
you must clear the interval if you does not need it more.
setTimeout(() => {
clearInterval(intervalId);
}, 10000);
I'm not sure if this will help but; Any function can be made into a promise and the alternative syntax [async keyword] might be useful for you in this case.
async function test() {
return "hello";
}
test().then( returned => console.log(returned)) // logs hello
setInterval() however does not return a return value rather it returns a "handle".
handle = window . setInterval( handler [, timeout [, arguments ] ] )
...
https://www.w3.org/TR/2011/WD-html5-author-20110705/spec.html#timers
You can, however, make promises from an setinterval ...
interval = window.setInterval(makepromise,1000)
async function makepromise() {
console.log("hello");
}
// or
interval = window.setInterval(async function () {console.log("hello");},1000)
But there is no place for a then then. We are back to callbacks, which we were trying to avoid! But there is functionality perhaps that we can use await within this function.
Better to make your calculateTimeRemaining to a promise And then you can use the then on the interval.
interval = window.setInterval(gameloop,1000);
function gameloop(endDate: string) {
calculateTimeRemaining(endDate: string).then(
//
// my then code goes here.
//
)
}
async calculateTimeRemaining(endDate: string) {
const { secondsInDay, daysOfYear, secondsInHour, secondsInMinute } = this.unitsOfTime;
let distance: number =
(Date.parse(new Date(endDate).toString()) - Date.parse(new Date().toString())) / this.increment;
if (distance > 0) {
// Years left
if (distance >= daysOfYear * secondsInDay) {
// 365.25 * 24 * 60 * 60
this.timeRemaining.years = Math.floor(distance / (daysOfYear * secondsInDay));
distance -= this.timeRemaining.years * daysOfYear * secondsInDay;
}
// Days left
if (distance >= secondsInDay) {
// 24 * 60 * 60
this.timeRemaining.days = Math.floor(distance / secondsInDay);
distance -= this.timeRemaining.days * secondsInDay;
}
// Hours left
if (distance >= secondsInHour) {
// 60 * 60
this.timeRemaining.hours = Math.floor(distance / secondsInHour);
distance -= this.timeRemaining.hours * secondsInHour;
}
// Minutes left
if (distance >= secondsInMinute) {
// 60
this.timeRemaining.minutes = Math.floor(distance / secondsInMinute);
distance -= this.timeRemaining.minutes * secondsInMinute;
}
// Seconds left
this.timeRemaining.seconds = distance;
}
return this.timeRemaining;
}
However, the value of promises is to avoid callback hell with an excessively complex call back scheme ... where the code is calling back, from call backs, from call backs, etc, etc, etc.
Promises do not operate in a 2nd operating system thread like a webworker. So unless you are attempting to clean up callbacks to make code readable or are actually waiting for something there is no benefit to make use of promises.
setInterval is a clean callback. The Gameloop example is not easier to read and understand because a promise was used. I would suggest in this case it is harder to read. at this point ... unless there are other awaits within the loop or a series of promises that do not need to run synchronously;
As already mentioned in the comments that you can not return promises on intervals, but you can keep them in a global object and use later,
const jobs = []
const interval = setInterval(() => {
if(jobs.length == 10) {
clearInterval(interval);
}
let job = Promise.resolve('new job created');
jobs.push(job);
console.log('job created')
}, 1000);
setTimeout(() => {
Promise.all(jobs).then(data => console.log(data))
}, 1000*15);
setInterval already return an integer which is useful to cancel this interval, using clearInterval.
const promise = new Promise((resolve, reject) => {
resolve('Hello');
});
Then use it like
promise.then((result) => {
console.log(result) // Says 'Hello' and will not resolve another value if we call it as it has already been resolved
})
Maybe this is what you tried to achieve.
If you want to call it with an interval of 1000 ms.
const getPromiseInstance = () => new Promise((resolve, reject) => {
resolve(Math.random());
});
setInterval(() => {
getPromiseInstance().then((result) => {
console.log(result)
})
}, 1000)
You should take a look to Observable, maybe it will fit your needs
Need to create a sequence of functions stored in an array. Need them to be executed every 400ms. Did it by setting 500 setTimeout(function(){})'s. Somehow learned that should do it by creating it dynamically. Have the functions ready, but missing the timeouts to make it a nice animation
Here is the part where I think I have to set the timeout's in:
function start() {
mylist.name = this.className;
mylist.identifier = 'port'+parseFloat(this.id);
mylist.number = parseFloat(this.id);
console.log(mylist.number);
for (var x in todo) {
todo[x]();
}
}
And here is the array with the functions. Thank you
ps: sorry for bad english, made in Germany :)
todo = [
function computer1() {
$('.'+mylist.name+':eq(1)').css({'background-color': 'chartreuse'});
},
function kabel1() {
$('.'+mylist.name+':eq(0)').css({'border-bottom-color': 'chartreuse'});
},
function port1() {
$('#'+mylist.identifier).css({'background-color': 'chartreuse'});
},
function port2() {
for (var x=0;x<ports;x++) {
$('#port'+(x+1)).css({'background-color': 'blue'});
if (x===(mylist.number-1)) {
$('#port'+(x+1)).css({'background-color': 'chartreuse'});
}
}
},
function kabel2() {
for (var x=0;x<ports;x++) {
$('#portugal'+(x+1)+':eq(0)').css({'border-bottom-color': 'blue'});
if (x===(mylist.number-1)) {
$('#portugal'+(x+1)+':eq(0)').css({'border-bottom-color': 'chartreuse'});
}
}
},
function device2() {
for (var x=0;x<ports;x++) {
$('#'+(x+1)+'device').css({'background-color': 'blue'});
if (x===(mylist.number-1)) {
$('#'+(x+1)+'device').css({'background-color': 'chartreuse'});
}
}
},
function device3() {
for (var x=0;x<ports;x++) {
if (document.getElementById('info').textContent==document.getElementById((x+1)+'device').className) {
var hammer = document.getElementById((x+1)+'device').getAttribute('class');
$('.'+hammer+':eq(1)').css({'background-color': 'red'});
}
}
},
function mehr() {
}]
Something like that?
let startTime = new Date().getTime();
let functions = [
()=>console.log("f0: " + (new Date().getTime() - startTime) + " ms passed"),
()=>console.log("f1: " + (new Date().getTime() - startTime) + " ms passed"),
()=>console.log("f2: " + (new Date().getTime() - startTime) + " ms passed"),
()=>console.log("f3: " + (new Date().getTime() - startTime) + " ms passed"),
()=>console.log("f4: " + (new Date().getTime() - startTime) + " ms passed"),
]
let i = 0;
setInterval(() => {
functions[i]();
i++;
if(i > functions.length - 1) i = 0;
}, 400);
If it worked always smash the accept button ;)
I'm not in favor of changing the array and modifying synchronous function. Thus changing for loop.
// Created variable using let to over come closure
for (let x in todo) {
setTimeout( ()=> {
todo[x]();
}, (x+1)*400);
}
You can avoid creating unnecessary closures or computing specific timeouts
const delay = (ms, x) =>
new Promise (r => setTimeout (r, ms, x))
const funcs =
[ () => console.log (1)
, () => console.log (2)
, () => console.log (3)
]
const forEachDelay = async (ms, [ f = () => {}, ...fs ]) =>
fs.length === 0
? f ()
: delay (ms, f ()) .then (() => forEachDelay (ms, fs))
forEachDelay (1000, funcs)
// 1
// (1000 ms later...) 2
// (1000 ms later...) 3
An advantage of using async is we get a Promise back and we know when the entire sequence is done –
forEachDelay (1000, funcs) .then (() => console.log ('done'), console.error)
// 1
// (1000 ms later...) 2
// (1000 ms later...) 3
// done
Promise is an effective data type because it allows you to combine smaller steps into one big-picture step.
Above we write forEachDelay using a functional expression, but we could also use an imperative-style for loop combined with the powerful await syntax –
const delay = (ms, x) =>
new Promise (r => setTimeout (r, ms, x))
const funcs =
[ () => console.log (1)
, () => console.log (2)
, () => console.log (3)
]
const forEachDelay = async (ms, fs = []) =>
{ for (const f of fs)
await delay (ms, f ())
}
forEachDelay (1000, funcs) .then (() => console.log ('done'), console.error)
// 1
// (1000 ms later...) 2
// (1000 ms later...) 3
// done
So my thinking was right, when recursing with promises, we end up calling all chained callbacks for however many times we recurse, for example
function p() {
return new Promise(function (r) {
process.nextTick(r);
})
}
function recurse(count) {
return p().then(function () {
if (count < 10) {
console.log('count => ', count);
return recurse(++count);
}
}).then(function(){
console.log('a');
return 5;
});
}
recurse(1).then(function () {
console.log('done');
});
If you run the above, we get:
count => 1
count => 2
count => 3
count => 4
count => 5
count => 6
count => 7
count => 8
count => 9
a
a
a
a
a
a
a
a
a
a
done
Is there a way to register this callback with console.log('a') just once instead of registering it 10 times?
I don't think I want/need this function to be called 10 times, and would like to find a way to have it called just once.
I am actually just as interested in a similar solution for Observables, specifically RxJS5 Observables.
I guess the only solution is to nest the remainder of your code inside the first promise callback like so:
function p() {
return new Promise(function (r) {
process.nextTick(r);
})
}
function recurse(count) {
return p().then(function () {
if (count < 10) {
return recurse(++count);
} else {
// all your remaining code would have to go here
console.log('a');
return 5; // or return someOtherPromise() or anything
}
});
}
recurse(1).then(function () {
console.log('done');
});
If the recursion is synchronous, you can simply recurse within the .then's function
new Promise(res => {
res(); // dummy initial promise
}).then(() => {
function recurse(x) { // recursion inside then
console.log('x', x);
if (x < 10) return recurse(++x);
return x;
}
return recurse(1); // begin recursion
}).then(y => { // this only fires once recursion above is resolved
console.log('y', y);
return 5;
}).then(z => console.log('z', z));
// x 1
// ... (synchronous)
// x 10
// y 10 (value passed from resolving inner promise)
// z 5 (value returned from previous then)
Or if our recursive function is asynchronous, we can have it return a promise too, so you end up with a recursion which looks like this
function doWork() {
function addOne(x) {
return new Promise((res, rej) => {
// async bit here
res(x + 1);
});
}
function recurse(x) {
if (x < 10) return addOne(x).then(recurse);
return x;
}
return recurse(1);
}
And in a promise chain, would look like this
new Promise(res => {
res(); // dummy initial promise
}).then(() => {
return doWork();
}).then(y => {
console.log(y);
});