JavaScript wait for async functions in while loop - javascript

I currently have 5 functions; each one uses setInterval to wait for an element to load and than clicks the element when it is available which leads to the next webpage. These functions also take place inside of a while loop. It is very important that these functions take place one after another and the while loop waits for all the functions to complete before looping again. Due to the functions being asynchronous the loop will run x times before any of the functions can even load.
Example of what I am trying to do:
function one () {
var checkForItem = setInterval(function () {
if ($('#element').length) {
$('#element').click();
clearInterval(checkForItem);
}
}, 100);
}
Imagine 5 of these functions (one, two, three, four, five), all with the same format using setInterval and the following while loop:
var x = 0, y = 10;
while (x < y){
one();
two();
three();
four();
five();
x++
}
How would I go about ensuring all the functions take place one after another before having the loop continue?
Note: I have tried using promises although due to the functions being async the loop still continues before the functions complete.

Use async/await syntax with the promises:
function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
async function one() {
while (!$('#element').length) {
await delay(100);
}
$('#element').click();
}
async function main() {
for (var x = 0; x < 10; x++) {
await one();
await two();
await three();
await four();
await five();
}
}

Define the selectors in an array, and gradually iterate over the array in the interval, until the end of the array is reached:
const initialSelectorsToFind = ['#element1', '#element2', '#element3']; // add more as desired
const elementSelectorsToFind = Array.from({ length: 10 })
.reduce(arrSoFar => [...arrSoFar, ...initialSelectorsToFind], []);
let elementIndexToFind = 0;
function tryClick(){
const elementToFind = $(elementSelectorsToFind[elementIndexToFind]);
if (elementToFind.length) {
elementToFind.click();
elementIndexToFind++;
if (elementIndexToFind === elementSelectorsToFind.length) {
clearInterval(tryClickInterval);
}
}
}
const tryClickInterval = setInterval(tryClick, 100);
But if you're trying to trigger a function (such as something that clicks an element) when an element gets added to the DOM, it would be far better off to use something that triggers when the add occurs, such as a callback in the creator function, or MutationObserver

Try wrap promise with async await:
async function one (){
await (new Promise(function(reolve, reject){
var checkForItem = setInterval(function () {
if ($('#element').length) {
$('#element').click();
clearInterval(checkForItem);
resolve();
}
}, 100);
}));
}
//And then in your while loop:
while (x < y){
await one();
await two();
...
x++
}
note: your while loop must be wrapped in an async function also.

Related

Any easier way to create CSS value change animations with javascript [duplicate]

I need to execute 3 functions in a 1 sec delay.
for simplicity those functions are :
console.log('1');
console.log('2');
console.log('3');
I could do this: ( very ugly)
console.log('1')
setTimeout(function () {
setTimeout(function () {
console.log('2')
setTimeout(function () {
console.log('3')
}, 1000)
}, 1000)
}, 1000)
Or I could create an array of functions and use setInterval with global counter.
Is there any elegant way of doing this ?
(p.s. function no.2 is not dependent on function number 1... hence - every sec execute the next function.).
You can use something like this with setTimeout:
var funcs = [func1, func2, func3],
i = 0;
function callFuncs() {
funcs[i++]();
if (i < funcs.length) setTimeout(callFuncs, 1000);
}
setTimeout(callFuncs, 1000); //delay start 1 sec.
or start by just calling callFuncs() directly.
Update
An setInterval approach (be aware of the risk of call stacking):
var funcs = [func1, func2, func3],
i = 0,
timer = setInterval(callFuncs, 1000);
function callFuncs() {
funcs[i++]();
if (i === funcs.length) clearInterval(timer);
}
Assuming you run it on a modern browser or have added support for array.map this is quite concise:
[func1, func2, func3].map(function (fun, index) {
setTimeout(fun, 1000 + index * 1000);
}
setTimeout(function(){console.log('1')}, 1000);
setTimeout(function(){console.log('2')}, 2000);
setTimeout(function(){console.log('3')}, 3000);
There is a new type of function declaration called generators in es6 (a.k.a ecmascript 6, es2015). It is incredibly useful for this situation, and makes your async code look synchronous. es6 is the latest standard of JavaScript as of 2015. It works on modern browsers but you can use Babel and its javascript polyfill to use generators now even on older browsers.
Here is a tutorial on generators.
The function myDelayedMessages below is an example of a generator. Run is a helper function that takes a generator function which it calls and provides a function to advance the generator as the first argument of the generator function that it called.
function delay(time, callback) {
setTimeout(function () {
callback();
}, time);
}
function run(generatorFunction) {
var generatorItr = generatorFunction(resume);
function resume(callbackValue) {
generatorItr.next(callbackValue);
}
generatorItr.next()
}
run(function* myDelayedMessages(resume) {
for(var i = 1; i <= 3; ++i) {
yield delay(1000, resume);
console.log(i);
}
});
This is an overview of the code which is similar to the above tutorial's final overview.
run takes our generator and creates a resume function. run creates a
generator-iterator object (the thing you call next on), providing
resume.
Then it advances the generator-iterator one step to kick
everything off.
Our generator encounters the first yield statement
and calls delay.
Then the generator pauses.
delay completes 1000ms later and calls resume.
resume tells our generator to advance a single step.
Our generator continues from the spot it yielded at then console.logs i, which is 1, then continues the loop
Our generator encounters the second call to yield,
calls delay and pauses again.
delay waits 1000ms and ultimately
calls the resume callback. resume advances the generator again.
Our generator continues from the spot it yielded at then console.logs i, which is 2, then continues the loop.
delay waits 1000ms and ultimately
calls the resume callback. resume advances the generator again.
Our generator continues from the spot it yielded at then console.logs i, which is 3, then continues and the loop finishes.
There are no more calls to yield, the generator finishes executing.
with async/await
const pause = _ => new Promise(resolve => setTimeout(resolve, _));
async function main() {
await pause(1000);
console.log('one');
await pause(1000);
console.log('two');
await pause(1000);
console.log('three');
}
main();
note this works in a loop too
const pause = _ => new Promise(resolve => setTimeout(resolve, _));
async function main() {
for (let i = 0; i < 3; ++i) {
await pause(1000);
console.log(i + 1);
}
}
main();
I think most simple way to do this is to create some closures within a function.
First i'll recall that you have big interest in using setInterval, since the overhead of the setTimeout might have it trigger 10ms off target. So especially if using short (<50ms) interval, prefer setInterval.
So we need to store the function array, the index of latest executed function, and an interval reference to stop the calls.
function chainLaunch(funcArray, time_ms) {
if (!funcArray || !funcArray.length) return;
var fi = 0; // function index
var callFunction = function () {
funcArray[fi++]();
if (fi==funcArray.length)
clearInterval(chainInterval);
} ;
var chainInterval = setInterval( callFunction, time_ms);
}
Rq : You might want to copy the function array ( funcArray = funcArray.slice(0); )
Rq2 : You might want to loop within the array
Rq3 : you might want to accept additionnal arguments to chainlaunch. retrieve them with var funcArgs = arguments.slice(3); and use apply on the functions : funcArray[fi++].apply(this,funcArgs);
Anyway the following test works :
var f1 = function() { console.log('1'); };
var f2 = function() { console.log('2'); };
var f3 = function() { console.log('3'); };
var fArr = [f1, f2, f3];
chainLaunch(fArr, 1000);
as you can see in this fiddle : http://jsfiddle.net/F9UJv/1/
(open the console)
There are here two methods. One with setTimeout and anotherone with setInterval. The first one is better in my opinion.
for(var i = 1; i++; i<=3) {
setTimeout(function() {
console.log(i);
}, 1000*i);
}
// second choice:
var i = 0;
var nt = setInterval(function() {
if(i == 0) return i++;
console.log(i++);
if(i>=3) clearInterval(nt);
}, 1000);

What happens if you make a function async and change nothing else?

I have a sudoku board implemented as an HTML table and a button that when clicked solves the sudoku board using a recursive backtracking algorithm in javascript. Now I want to make it so that you can see the adjustments being made by not altering the HTML immediately in my recursive function. I tried making the function async and then calling this function
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
like so
for (let choice = 1; choice <= 9; choice++) {
this.boardArray[row][col] = choice;
// put delay here
await sleep(250);
currEntry.textContent = choice;
if (this.choiceOkay(row, col)) {
const solved = this.solveBoard(nextRow, nextCol);
if (solved) {
return true;
}
}
This does give me the desired behavior initially but only for a portion of the board and then it seems to just stop. I removed the calls to the sleep function and tried it with the only alteration being "async" in front of the function declaration and it still only did a portion of the board but this time all at once without the visual delay. I'm wondering why making this function async causes this logic error?
Thanks in advance!
Also, this is my first question on Stack Overflow so let me know if I need to be more specific or anything along those lines.
The async keyword in front of a function basically says:
This function will return a Promise
Your sleep function already returns a Promise, so writing the keyword async is useless.
What matters is the await keyword which basically says:
On my right, there may be an async function (idem a Promise). Wait for my return before continuing
If you write async function sleep, but omit the await keyword when you call sleep, you throw the function call in the nature and never waits for its return to come back (hence your code running "without delay")
If you want delay, it is better to let your algorithm be as is, and let the caller of your algorithm tell your algorithm to continue or not.
You can for that matter use generators.
(An other possibility could be trampolining).
function* runAlgo () {
for (let choice = 1; choice <= 9; choice++) {
yield; // gives control back to delayer
console.log('choice : ', choice)
// do your algo and put some yield wherever you like
}
}
// no need for async, a Promise is already returned
function sleep (t) {
return new Promise((ok, ko) => setTimeout(ok, t))
}
async function delayer () { // need for async so we can use the await keyword below
const it = runAlgo()
let next = it.next()
while (!next.done) {
await sleep(1000)
next = it.next()
}
}
delayer()
However, it is very likely that solving your board is what freezes your ui.
So you want to wait inside of solvingBoard as well:
function* solveBoard () {
let i = 0
while (i < 5) { //freezes the ui if not yielding
yield i++
}
}
function* runAlgo () {
for (let choice = 1; choice <= 9; choice++) {
yield 'choice : '+choice; // gives control back to caller
yield* solveBoard()
}
}
// no need for async, a Promise is already returned
function sleep (t) {
return new Promise((ok, ko) => setTimeout(ok, t))
}
async function delayer () { // need for async so we can use the await keyword below
const it = runAlgo()
let next = it.next()
while (!next.done) {
await sleep(1000)
next = it.next()
console.log('data', next.value)
}
}
delayer()

End a function only after the end of my interval. (Javascript)

I have a question regarding a personal project I'm working. I need to create a fade-in and fade-out effect for an object without CSS, and I'm having a hard time with my intervals and function, because I need to finish the fadeout function before moving to the next set of functions.
// Interval parameters
var bgtInterval = [0, false, 50];
// Fade functions will change fill following brightStep array values.
function fadeOut(){
let i = 11;
OBJECT.setAttribute("fill", brightSteps[i]);
bgtInterval[1]=true;
bgtInterval[0] = setInterval(function(){
FULLCLOCK.setAttribute("fill", brightSteps[i]);
if (i<=0){
clearInterval(bgtInterval[0]);
bgtInterval[1]=false;
}
i--;
}, bgtInterval[2]);
}
function fadeIn(){
let i = 0;
OBJECT.setAttribute("fill", brightSteps[i]);
bgtInterval[1]=true;
bgtInterval[0] = setInterval(function(){
FULLCLOCK.setAttribute("fill", brightSteps[i]);
if (i>=11){
clearInterval(bgtInterval[0]);
bgtInterval[1]=false;
}
i++;
}, bgtInterval[2]);
}
-
function resetSettings(id){
fadeOut(); // I need fadeOut to finish before I move forward
displayReset();
displayShow();
fadeIn(); // I need fadeIn to finish before I move out of this function
}
//THE MAIN FUNCTION//
resetSettings();
moreFunctionsHere();
Thank You!!!
Use Promises and async/await to achieve this very simply
var bgtInterval = [0, false, 50];
// Fade functions will change fill following brightStep array values.
function fadeOut(){
// return the Promise
return new Promise(resolve => {
let i = 11;
OBJECT.setAttribute("fill", brightSteps[i]);
bgtInterval[1]=true;
bgtInterval[0] = setInterval(function(){
FULLCLOCK.setAttribute("fill", brightSteps[i]);
if (i<=0){
clearInterval(bgtInterval[0]);
bgtInterval[1]=false;
// resolve when done
resolve();
}
i--;
}, bgtInterval[2]);
});
}
function fadeIn(){
// return a Promise
return new Promise(resolve => {
let i = 0;
OBJECT.setAttribute("fill", brightSteps[i]);
bgtInterval[1]=true;
bgtInterval[0] = setInterval(function(){
FULLCLOCK.setAttribute("fill", brightSteps[i]);
if (i>=11){
clearInterval(bgtInterval[0]);
bgtInterval[1]=false;
// resolve when done
resolve();
}
i++;
}, bgtInterval[2]);
});
}
// use async so we can await a Promise
async function resetSettings(id){
// wait for fadeOut to finish
await fadeOut();
displayReset();
displayShow();
await fadeIn();
}
//THE MAIN FUNCTION//
// as we are in global scope, you can't use `async` ... so use Promise.then instead
resetSettings().then(moreFunctionsHere);
You'll see that I'm using .then in the last line, because in the global scope you can't use await
You could, however do something like
(async () => {
await resetSettings();
moreFunctionsHere();
})();
an async IIFE so you can await

Create a function that no matter how many times invoked run only when first async call finishes?

suppose I've a function fetch(id).
I would be calling it arbitrarily at random times.
But I want every successive call to run only after previous call has finished.
say the async task takes 4 seconds, and i call fetch 3 times. then total time should be 12 seconds.
I could make a array and at every call set promise to go through next in line.
but what are some ways to accomplish this.
I think I got it
//First my example function which could be anything but should return promise which would be queued
function example(n) {
return new Promise((res, rej) => {
setTimeout(()=> {
console.log(n);
res();
}, 1000);
});
}
//now solution
function debounce(func) {
let p = Promise.resolve();
return function(x){
p = p.then(() => func(x));
}
}
//usage
d = debounce(example);
d(1);d(2);d(3);d(4);d(5);d(6);d(1);
You can chain Promises without an array, just store a pointer to the last Promise
// async payload function
// returns a promise
function f(x) {
console.log(`call f(${x})`);
return new Promise((resolve) => {
setTimeout(() => {
console.log(`resolve f(${x})`);
resolve();
}, 2000);
});
}
// wrapper to call function `f` sequentially
// stores pointer to the last Promise in closure
const g = (function(){
let lastPromise = Promise.resolve();
return function(arg){
lastPromise = lastPromise.then(() => f(arg));
}
})();
// generate random calls of function function `g`
for (let i = 0; i < 5; i++) {
setTimeout(() => g(i), Math.random() * 100);
}
I guess you can use async.io library.
Or, each time you want to call your function, push the function itself in an array. When the previous function has finished, check if there is still some functions to call in the array.

how to add a random delay with setTimeout using async or promises

if i want to print a numbers from 1 to 10 using setTimeout and delay = Math.random() * 1000.
Answer would be number from 1 to 10 in random order because of async programming and event loop.
What i want is to print the number in increasing order with same delay above mentioned. This can be done via Promises or Async module. What i mean to say is it should only proceed once number 1 in printed then 2 so on.
Any help would be appreciated.
NOTE : Please dont give answers like adding time to a variable and using that variable as a delay.
You could do this like this, using Promises and async/await
// returns a promise that resolves after the specified number of ms
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// function that will print the numbers in correct order, with delays
async function print(num) {
for (let i = 1; i <= num; i++) {
await delay(Math.random() * 1000); // wait
console.log(i); // print number
}
}
print(10); // actually execute function
The function that actually prints the numbers is an async function, using a delay based on a promise that resolves after the specified number of milliseconds.
es6 fromat
const delay = (m) => new Promise(resolve => setTimeout(resolve, m));
const print = async (num) => {
for (let i = 1; i <= num; i++) {
await delay(Math.random() * 1000);
console.log(i);
}
};
print(10);
I think you want a semi recursive setTimeout:
(function print(value){
console.log(value);
if(value<10) setTimeout(print,Math.random()*1000,value+1);
})(1);
You need to use setInterval and not setTimeout like this
var count = 1;
var printSequence;
function myFunction() {
//using setInterval that is referenced by a variable
printSequence = setInterval(print, Math.random()*1000);
}
function print(){
console.log(count);
count++;;
clearInterval(printSequence);
if(count <= 10){
myFunction();
}
}
<button onclick="myFunction()">Try it</button>
No need to add extra logic and code here.

Categories