Javascript delay a loop till iframe is loaded in first iteration - javascript

I have a javascript loop that executes a couple of functions. The first function loads an iframe and the second function clicks an element on that iframe. I want the loop not to run the second function until the iframe is finished loading. But I am not sure how to achieve this.
So far I have done this but doesn't seem to do the job
Loop :
action.steps.forEach(step => {
window[step.functionName](step.functionParameter);
});
First function
function goToUrl(url) {
let iframeDocument = document.querySelector('iframe');
iframeDocument.src = url;
let iframeLoaded;
iframeDocument.onload = () => {
iframeLoaded = true
}
async function checkLoad() {
if (iframeLoaded) {
alert("page loaded");
return true;
} else {
await sleep(500);
checkLoad();
}
}
checkLoad();
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
second function
function clickOnElement(elementSelector) {
var element = iframeDocument.querySelector(elementSelector);
element.click();
}

The first function is an asynchronous operation, which can be modified to return a Promise that resolves when the frame is loaded. This also means you don't need to recursively checkLoad.
function goToUrl(url) {
const iframeDocument = document.querySelector('iframe');
iframeDocument.src = url;
return new Promise((resolve, reject) => {
// calls `resolve()` when loaded
iframeDocument.onload = resolve;
});
}
The second function needs to wait for the first function to be resolved.
To generalise this pattern, you can modify your loop as an asynchronous function,
which awaits for the result of a step if that step's function returns a Promise (e.g. your goToUrl function:
async function yourLoop() {
// each step could be synchronous or asynchronous
for (const step of actions.step) {
const result = window[step.functionName](step.functionParameter);
if (result instanceof Promise) {
// if step is asynchronous operation, wait for it to complete
await result;
}
}
}
/////// usage ////////
yourLoop().then(() => {
/* all steps completed */
}).catch(() => {
/* some step(s) failed */
});

Whenever a timer or event handler is needed, I usually prefer not to do a traditional loop nor mess with promises/awaits.
Instead what I would do is something similar to this. Basically waiting in onload to run the callback function then moving on to the next function in the loop.
let max = action.steps.length;
let cur = 0;
function loadIframe(url) {
let iframeDocument = document.querySelector('iframe');
iframeDocument.src = url;
let iframeLoaded;
iframeDocument.onload = () => {
let step = action.steps[cur];
window[step.functionName](step.functionParameter);
if(cur < max){
cur++;
loadIframe(step.url)
}
}
}
loadIframe(action.steps[cur].url);

Related

Javascript: Pause while a condition is true

I am interfacing with a third party SDK in my javascript code. I make func1 and func2. Func1 is wired to a "onFunc1" event where I keep track of a flag. When the flag is false, I try to call Func2. My objective is to issue Func1, wait until it finishes(the only way I know it finishes is when onFunc1 event is fired) and then, issue Func2. This is how my code is:
var _connected = false;
var _customWindow = CustomWindow.initialize();
_customWindow.on('Func1', function(){ window._connected = false; });
const delay = ms => new Promise(res => setTimeout(res, ms));
function goToNextWindow(){
try{
_customWindow.Func1();
} catch(err){console.log('error calling Func1');}
console.log('wait for Func1 to finish');
while(window._connected === true){
(async () => {
await delay(300);
})();
}
console.log('Func1 execution is done');
if(window._connected === false){_customWindow.Func2();}
}
function button_click(){
goToNextWindow();
}
I'm trying to make sure that Func1 is done executing before I issue the Func2 call. Is this a correct way of accomplishing my task? Can this be simplified?
Any help is appreciated.
You can create a promise that will resolve when you set window._connected to false. Then you can wait for that promise to resolve in your function:
let _connected = false;
let _customWindow = CustomWindow.initialize();
const waitToBeFalse = new Promise((resolve) => {
_customWindow.on('Func1', function(){
window._connected = false;
resolve();
});
});
async function goToNextWindow(){
try{
_customWindow.Func1();
} catch(err){console.log('error calling Func1');}
console.log('wait for Func1 to finish');
await waitToBeFalse;
if(window._connected === false){_customWindow.Func2();}
}

JS: Unsure of when to use Promise.resolve() vs returning a new Promise

I am unsure how the usage of returning a new Promise vs using a Promise.resolve() and want to make sure my understanding of these are correct.
Given these 3 functions below:
function testFunc() {
return Promise.resolve().then(() => {
//anything asynchronous in here has now become synchronous and
//execution of result of the function happens after this??
let i = 0;
while (i < 10000) {
console.log(i);
i++;
}
});
}
------
function testFunc2() {
return new Promise((resolve, reject) => {
//anything asynchronous in here is still asynchronous but the
//`resolve()` is then synchronous??
let i = 0;
while (i < 10000) {
if (i === 999) { resolve('I am a test func') };
i++;
}
})
}
------
//synchronous function
function logMe() {
let i = 0;
while (i < 10000) {
console.log("INSIDE LOG ME);
i++;
}
}
The way I understand it is that testFunc() immediately resolves the Promise and anything within there becomes synchronous. So if you were to execute:
testFunc();
logMe();
testFunc() would fully execute before logMe() was reached and executed.
For the testFunc2(), if it were executed in this order:
testFunc2();
logMe();
I understand it as the logic inside, in this case the while loop, would still execute synchronously and delay the execution of the following function, logMe(), but the resolve would be treated asynchronously.
It’s not that easy. You would have to use test Func.then(logMe()). Another option would be to run both functions in an asynchronous function:
async function run() {
await testFunc();
logMe();
}
The await is pretty simple - it runs the function and waits for it to finish, then runs the next lines. async is just there so that await can work (await does not work outside of async functions).
I prefer async/await, but many more prefer .then. It’s just what you want to use, both are very similar.
If I understand correctly what you're trying to achieve, you want to asynchronously execute operation inside the loop.
function testFunc2() {
const promises = []
for(let i = 0; i<1000; i++){
promises.push(new Promise((resolve) => {
console.log(i);
resolve()
}));
}
return Promise.all(promises);
}
function logMe() {
let i = 0;
while (i < 5) {
console.log("INSIDE LOG ME");
i++;
}
}
(async() => {
await testFunc2()
logMe();
})();

async function external stack context

Sometimes code would like to know if a particular function (or children) are running or not. For instance, node.js has domains which works for async stuff as well (not sure if this includes async functions).
Some simple code to explain what I need would by like this:
inUpdate = true;
try {
doUpdate();
} finally {
inUpdate = false;
}
This could then be used something like:
function modifyThings() {
if (inUpdate) throw new Error("Can't modify while updating");
}
With the advent of async this code breaks if the doUpdate() function is asynchronous. This was of course already true using callback-style functions.
The doUpdate function could of course be patched to maintain the variable around every await, but even if you have control over the code, this is cumbersome and error prone and this breaks when trying to track async function calls inside doUpdate.
I tried monkey-patching Promise.prototype:
const origThen = Promise.prototype.then;
Promise.prototype.then = function(resolve, reject) {
const isInUpdate = inUpdate;
origThen.call(this, function myResolve(value) {
inUpdate = isInUpdate;
try {
return resolve(value);
} finally {
inUpdate = false;
}
}, reject);
}
Unfortunately this doesn't work. I'm not sure why, but the async continuation code ends up running outside of the resolve call stack (probably using a microtask).
Note that it's not enough to simply do:
function runUpdate(doUpdate) {
inUpdate = true;
doUpdate.then(() => inUpdate = false).catch(() => inUpdate = false);
}
The reason is:
runUpdate(longAsyncFunction);
console.log(inUpdate); // incorrectly returns true
Is there any way to track something from outside an async function so it's possible to tell if the function called, or any of its descendant calls are running?
I know that it's possible to simulate async functions with generators and yield, in which case we have control over the call stack (since we can call gen.next()) but this is a kludge which the advent of async functions just got around to solving, so I'm specifically looking for a solution that works with native (not Babel-generated) async functions.
Edit: To clarify the question: Is there's a way for outside code to know if a particular invocation of an async function is running or if it is suspended, assuming that this code is the caller of the async function. Whether it's running or not would be determined by a function that ultimately is called by the async function (somewhere in the stack).
Edit: To clarify some more: The intended functionality would be the same as domains in node.js, but also for the browser. Domains already work with Promises, so async functions probably work as well (not tested).
This code allows me to do what I want to a certain extent:
function installAsyncTrack() {
/* global Promise: true */
if (Promise.isAsyncTracker) throw new Error('Only one tracker can be installed');
const RootPromise = Promise.isAsyncTracker ? Promise.rootPromise : Promise;
let active = true;
const tracker = {
track(f, o, ...args) {
const prevObj = tracker.trackObj;
tracker.trackObj = o;
try {
return f.apply(this, args);
} finally {
tracker.trackObj = prevObj;
}
},
trackObj: undefined,
uninstall() {
active = false;
if (Promise === AsyncTrackPromise.prevPromise) return;
if (Promise !== AsyncTrackPromise) return;
Promise = AsyncTrackPromise.prevPromise;
}
};
AsyncTrackPromise.prototype = Object.create(Promise);
AsyncTrackPromise.rootPromise = RootPromise;
AsyncTrackPromise.prevPromise = Promise;
Promise = AsyncTrackPromise;
AsyncTrackPromise.resolve = value => {
return new AsyncTrackPromise(resolve => resolve(value));
};
AsyncTrackPromise.reject = val => {
return new AsyncTrackPromise((resolve, reject) => reject(value));
};
AsyncTrackPromise.all = iterable => {
const promises = Array.from(iterable);
if (!promises.length) return AsyncTrackPromise.resolve();
return new AsyncTrackPromise((resolve, reject) => {
let rejected = false;
let results = new Array(promises.length);
let done = 0;
const allPromises = promises.map(promise => {
if (promise && typeof promise.then === 'function') {
return promise;
}
return new AsyncTrackPromise.resolve(promise);
});
allPromises.forEach((promise, ix) => {
promise.then(value => {
if (rejected) return;
results[ix] = value;
done++;
if (done === results.length) {
resolve(results);
}
}, reason => {
if (rejected) return;
rejected = true;
reject(reason);
});
});
});
};
AsyncTrackPromise.race = iterable => {
const promises = Array.from(iterable);
if (!promises.length) return new AsyncTrackPromise(() => {});
return new AsyncTrackPromise((resolve, reject) => {
let resolved = false;
if (promises.some(promise => {
if (!promise || typeof promise.then !== 'function') {
resolve(promise);
return true;
}
})) return;
promises.forEach((promise, ix) => {
promise.then(value => {
if (resolved) return;
resolved = true;
resolve(value);
}, reason => {
if (resolved) return;
resolved = true;
reject(reason);
});
});
});
};
function AsyncTrackPromise(handler) {
const promise = new RootPromise(handler);
promise.trackObj = tracker.trackObj;
promise.origThen = promise.then;
promise.then = thenOverride;
promise.origCatch = promise.catch;
promise.catch = catchOverride;
if (promise.finally) {
promise.origFinally = promise.finally;
promise.finally = finallyOverride;
}
return promise;
}
AsyncTrackPromise.isAsyncTracker = true;
function thenOverride(resolve, reject) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origThen.apply(this, arguments);
return this.origThen.call(
this,
myResolver(trackObj, resolve),
reject && myResolver(trackObj, reject)
);
}
function catchOverride(reject) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origCatch.catch.apply(this, arguments);
return this.origCatch.call(
this,
myResolver(trackObj, reject)
);
}
function finallyOverride(callback) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origCatch.catch.apply(this, arguments);
return this.origCatch.call(
this,
myResolver(trackObj, reject)
);
}
return tracker;
function myResolver(trackObj, resolve) {
return function myResolve(val) {
if (trackObj === undefined) {
return resolve(val);
}
RootPromise.resolve().then(() => {
const prevObj = tracker.trackObj;
tracker.trackObj = trackObj;
RootPromise.resolve().then(() => {
tracker.trackObj = prevObj;
});
});
const prevObj = tracker.trackObj;
tracker.trackObj = trackObj;
try {
return resolve(val);
} finally {
tracker.trackObj = prevObj;
}
};
}
}
tracker = installAsyncTrack();
function track(func, value, ...args) {
return tracker.track(func, { value }, value, ...args);
}
function show(where, which) {
console.log('At call', where, 'from', which, 'the value is: ', tracker.trackObj && tracker.trackObj.value);
}
async function test(which, sub) {
show(1, which);
await delay(Math.random() * 100);
show(2, which);
if (sub === 'resolve') {
await Promise.resolve(test('sub'));
show(3, which);
}
if (sub === 'call') {
await test(which + ' sub');
show(3, which);
}
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
track(test, 'test1');
track(test, 'test2');
track(test, 'test3', 'resolve');
track(test, 'test4', 'call');
It replaces the native Promise with my own. This promise stores the current context (taskObj) on the promise.
When the .then callback or its ilk are called, it does the following:
It creates a new native promise that immediately resolves. This adds a new microtask to the queue (according to spec, so should be reliable).
It calls the original resolve or reject. At least in Chrome and Firefox, this generates another microtask onto the queue that will run next part of the async function. Not sure what the spec has to say about this yet. It also restores the context around the call so that if it's not await that uses it, no microtask gets added here.
The first microtask gets executed, which is my first (native) promise being resolved. This code restores the current context (taskObj). It also creates a new resolved promise that queues another microtask
The second microtask (if any) gets executed, running the JS in the async function to until it hits the next await or returns.
The microtask queued by the first microtask gets executed, which restores the context to what it was before the Promise resolved/rejected (should always be undefined, unless set outside a tracker.track(...) call).
If the intercepted promise is not native (e.g. bluebird), it still works because it restores the state during the resolve(...) (and ilk) call.
There's one situation which I can't seem to find a solution for:
tracker.track(async () => {
console.log(tracker.taskObj); // 'test'
await (async () => {})(); //This breaks because the promise generated is native
console.log(tracker.taskObj); // undefined
}, 'test')
A workaround is to wrap the promise in Promise.resolve():
tracker.track(async () => {
console.log(tracker.taskObj); // 'test'
await Promise.resolve((async () => {})());
console.log(tracker.taskObj); // undefined
}, 'test')
Obviously, a lot of testing for all the different environments is needed and the fact that a workaround for sub-calls is needed is painful. Also, all Promises used need to either be wrapped in Promise.resolve() or use the global Promise.
[is it] possible to tell if the function called, or any of its descendant calls are running?
Yes. The answer is always no. Cause there is only one piece of code running at a time. Javascript is single threaded per definition.
Don't make it any more complicated than it needs to be. If doUpdate returns a promise (like when it is an async function), just wait for that:
inUpdate = true;
try {
await doUpdate();
//^^^^^
} finally {
inUpdate = false;
}
You can also use the finally Promise method:
var inUpdate = true;
doUpdate().finally(() => {
inUpdate = false;
});
That'll do just like like your synchronous code, having inUpdate == true while the function call or any of its descendants are running. Of course that only works if the asynchronous function doesn't settle the promise before it is finished doing its thing. And if you feel like the inUpdate flag should only be set during some specific parts of the doUpdate function, then yes the function will need to maintain the flag itself - just like it is the case with synchronous code.

Check for variable with javascript promise

I have two variables where certain values needs to be fulfilled and then call a function. One of the variables get set from an onComplete call after an js-animation is finished. The other is called once a video-file is completed preloading. My problem is that I don't know which one that will be called first. Therefore I would like to check so that both values are fulfilled with a promise.
They both get their values set in two different callback functions.
I have this but I don't understand how to use the callbacks with Promise.
// Callback 1:
function nextSlide(event){
finishedAnim = true;
};
// Callback 2:
function handleNextFileComplete(event) {
nextVideoEl.src = nextVideo;
nextfileLoaded = "complete";
};
Promise.all([
]).then(() => {
slider.next();
});
One solution would be using promises instead of control variables. Let's say you have a function to start the animation and other to start loading the video file.
function startAnimation() {
return new Promise((resolve, reject) => {
// Start the animation and pass the onAnimationCompleted callback
function onAnimationCompleted(event) {
resolve();
}
});
}
function startLoadingVideo() {
return new Promise((resolve, reject) => {
// Start loading the video and pass the onVideoLoaded callback
function onVideoLoaded(event) {
resolve();
}
});
}
Now you can call both functions and use the function Promise.all() to handle the promises. The calling method would be something like:
let animationPromise = startAnimation();
let videoPromise = startLoadingVideo();
Promise.all([animationPromise, videoPromise])
.then(() => slider.next());
I realised that this beacame kind of complex situation.
First I have timer with this code:
TweenMax.delayedCall(7.3, delayCallComplete);
function delayCallComplete(event){
finished = true;
};
Then I have preloading of video with this code:
function loadNextVideo() {
var preloadNext = new createjs.LoadQueue(true);
preloadNext.addEventListener("fileload", handleNextFileComplete);
preloadNext.loadFile(nextVideo);
};
function handleNextFileComplete(event) {
nextVideoEl.src = nextVideo;
nextfileLoaded = "complete";
};
I want to check that both of these cases has happened before calling a function containing slider.next();.

ES6 promises with timeout interval

I'm trying to convert some of my code to promises, but I can't figure out how to chain a new promise inside a promise.
My promise function should check the content of an array every second or so, and if there is any item inside it should resolve. Otherwise it should wait 1s and check again and so on.
function get(){
return new Promise((resolve) => {
if(c.length > 0){
resolve(c.shift());
}else{
setTimeout(get.bind(this), 1000);
}
});
}
let c = [];
setTimeout(function(){
c.push('test');
}, 2000);
This is how I expect my get() promise function to work, it should print "test" after 2 or 3 seconds max:
get().then((value) => {
console.log(value);
});
Obviously it doesn't work, nothing is ever printed
setTimeout has terrible chaining and error-handling characteristics on its own, so always wrap it:
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
function get(c) {
if (c.length) {
return Promise.resolve(c.shift());
}
return wait(1000).then(() => get(c)); // try again
}
let c = [];
get(c).then(val => console.log(val));
wait(2000).then(() => c.push('test'));
While you didn't ask, for the benefit of others, this is a great case where async/await shines:
const wait = ms => new Promise(r => setTimeout(r, ms));
async function get(c) {
while (!c.length) {
await wait(1000);
}
return c.shift();
}
let c = [];
get(c).then(val => console.log(val));
wait(2000).then(() => c.push('test'));
Note how we didn't need Promise.resolve() this time, since async functions do this implicitly.
The problem is that your recursive call doesn't pass the resolve function along, so the else branch can never call resolve.
One way to fix this would be to create a closure inside the promise's callback so that the recursive call will have access to the same resolve variable as the initial call to get.
function get() {
return new Promise((resolve) => {
function loop() {
if (c.length > 0) {
resolve(c.shift());
} else {
setTimeout(loop, 1000);
}
}
loop();
});
}
let c = [];
setTimeout(function() {
c.push('test');
}, 2000);
get().then(val => console.log(val));
In the else case, you never resolve that promise. get might create another one, but it is returned to nowhere.
You should promisify your asynchronous function (setTimeout) on the lowest level, and then only chain your promises. By returning the result of the recursive call from a then callback, the resulting promise will resolve with the same result:
function delayAsync(time) {
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
function get(c) {
if (c.length > 0){
return Promise.resolve(c.shift());
} else {
return delay(1000).then(() => {
return get(c); // try again
});
}
}
What you need is a polling service, which checks periodically for specific condition prior proceeding with promise resolution. Currently when you run setTimeout(get.bind(this), 1000); you are creating a new instance of the promise without actually resolving the initial promise, because you don't reference to the initial resolve function that you created.
Solution:
Create a new callback function that you can reference to it inside the promise
Pass the resolve & reject as params in the setTimeout invocation e.g. setTimeout(HandlePromise, 1000, resolve, reject, param3, param4 ..); setTimeout API
function get() {
var handlerFunction = resolve => {
if (c.length > 0) {
resolve(c.shift());
} else {
setTimeout(handlerFunction, 1000, resolve);
}
};
return new Promise(handlerFunction);
}
let c = [];
setTimeout(function() {
c.push("test");
}, 2000);
get().then(value => {
console.log(value);
});
For more information look into javascript polling article
You could try this solution. Since JS needs to free itself to download the images, I use await within an asynchronous function and an asynchronous call to wake up JS after a delay
private async onBeforeDoingSomething() : void {
await this.delay(1000);
console.log("All images are loaded");
}
private delay (ms : number = 500) : Promise<number> {
return new Promise((resolve,reject) => {
const t = setTimeout( () => this.areImgsLoaded(resolve), ms);
});
}
private async areImgsLoaded (resolve) {
let reload = false;
const img = document.querySelectorAll('img');
console.log("total of images: ",img.length);
for (let i = 0; i < img.length; i++){
if (!img[i]["complete"]) {
console.log("img not load yet");
reload = true;
break;
}
}
if (reload) {
await this.delay();
}
resolve();
}
Use setInterval to check every second. Run this script to understand.
let c = [];
function get(){
return new Promise((resolve) => {
var i = setInterval(function(){
if(c.length > 0){
resolve(c.shift());
clearInterval(i);
}
}, 1000);
});
}
setTimeout(function(){
c.push('test');
}, 2000);
get().then((value) => {
console.log(value);
});

Categories

Resources