Async function in for loop - javascript

I'm trying to lazy load maps in the page. As the map element intersects, I load the script an run the appendMap() function. If two maps are in the viewport, the loadScript function test for map-scipt id is faster than the script loading, and I have an error because appendMap function does not exists.
How can I make this functions async to wait for the script loading at least one time ?
// Intersection Observer
const lazyInit = (element,margin,fn) => {
const observer = new IntersectionObserver((entries) => {
if (entries.some(({isIntersecting}) => isIntersecting)) {
observer.disconnect();
fn();
}
},{rootMargin: margin + 'px'});
observer.observe(element);
};
// Load script
function loadScript(url,id,fn){
var s = document.getElementById(id);
if(!s){
s = document.createElement('script');
s.id = id;
s.src = url;
s.async = true;
document.head.appendChild(s);
s.onload = function(){
fn();
}
} else {
fn();
}
}
// Maps
for(const map of document.querySelectorAll('.map')){
lazyInit(map,600,() => {
loadScript('/map-script.js','map-script',() => {
appendMap(map);
});
});
}

Can you try to make the function lazyInit async ?
const lazyInit = async (element,margin,fn) => {
const observer = new IntersectionObserver((entries) => {
if (entries.some(({isIntersecting}) => isIntersecting)) {
observer.disconnect();
await fn();
}
},{rootMargin: margin + 'px'});
observer.observe(element);
};
for (const map of document.querySelectorAll('.map')) {
const lazyResp = lazyInit(map, 600, () => {
loadScript('/map-script.js','map-script',() => {
appendMap(map);
});
});
}

Related

text animation script only works once after cache is cleared on safari [react]

I have a text animation script scramble.js that works fine on chrome, but stops working after a reload on safari. It started working after I cleared the cache on safari, up until I reload the page. Project was built using create-react-app. I loaded the script through html onto a different project that doesn't use react and there it works flawlessly on safari.
On checking the networks tab, on both chrome and safari, bundle.js gets a 200 or a 304 response randomly after reloading.
This is my scramble.js
window.onload = function () {
const links = document.querySelectorAll("span.cipher");
const solveMilliseconds = 1000;
const characterSelectionMilliseconds = 50;
const delayMilliseconds = 100;
const characters = [..."qwertyuioplkjhgfdsazxcvbnm!<>-_\\/[]{}—=#$&+*^?#________"];
const randomArrayElement = (arr) => {
return arr[(arr.length * Math.random()) | 0];
};
links.forEach((element) => {
element.addEventListener("mouseenter", (e) => {
const element = e.target;
scrambleText(element);
e.preventDefault();
});
});
function scrambleText(element) {
if (element.classList.contains("active") === false) {
let delay = 0;
const elementText = element.innerText;
const elementCharacters = [...elementText];
const lockMilliseconds =
delayMilliseconds * elementCharacters.length + solveMilliseconds;
element.classList.add("active");
setTimeout(() => {
element.classList.remove("active");
}, lockMilliseconds);
elementCharacters.forEach((character, index) => {
setTimeout(
() => {
let intervalId = setInterval(() => {
const randomCharacter = randomArrayElement(characters);
element.innerText = replaceCharacter(
element.innerText,
index,
randomCharacter
);
setTimeout(() => {
clearInterval(intervalId);
element.innerText = replaceCharacter(
element.innerText,
index,
elementCharacters[index]
);
}, solveMilliseconds);
}, characterSelectionMilliseconds);
},
delay === 0 ? (delay += 1) : (delay += delayMilliseconds)
);
});
}
}
function replaceCharacter(str, index, chr) {
return `${str.substring(0, index)}${chr}${str.substring(index + 1)}`;
}
};

Wait for all dynamic script-src tags to load before executing addEventListener() function

my code:
Util = {
load_css : function(urls){
for ( var i = 0; i < urls.length; i++){
var head = document.head;
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = urls[i];
link.className = 'dynamic-css'
head.appendChild(link);
}
document.querySelector('.dynamic-css').addEventListener('load', event => {
Template.render();
})
},
load_js : function(urls){
for ( var i = 0; i < urls.length; i++){
var script = document.createElement('script');
script.setAttribute('src', urls[i]);
script.setAttribute('class', 'dynamic-js');
script.setAttribute('async', false);
document.head.appendChild(script);
}
document.querySelector('.dynamic-js').addEventListener('load', event => {
Template.render();
})
}
}
the urls parameter for load_css and load_js is just an array of urls that I want to load (obviously js urls for load_js and css urls for load_css). Currently, I don't think the addEventListener('load' ...) waits for all items to load; just one. How can I make it wait for all items?
Thanks in advance
Promisify the load listeners and use Promise.all.
Promise.all(
urls.map((url) => {
var script = document.createElement('script');
script.setAttribute('src', url);
script.setAttribute('class', 'dynamic-js');
script.setAttribute('async', false);
return new Promise((resolve, reject) => {
script.addEventListener('load', resolve);
script.addEventListener('error', reject);
document.head.appendChild(script);
});
})
)
.then(() => {
Template.render();
})
.catch((error) => {
// at least one script had an error
});
Live demo:
const urls = [
'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js'
];
Promise.all(
urls.map((url) => {
var script = document.createElement('script');
script.setAttribute('src', url);
script.setAttribute('class', 'dynamic-js');
script.setAttribute('async', false);
return new Promise((resolve, reject) => {
script.addEventListener('load', resolve);
script.addEventListener('error', reject);
document.head.appendChild(script);
});
})
)
.then(() => {
console.log(typeof $);
console.log(typeof _);
})
.catch((error) => {
// at least one script had an error
});
If order matters, this loads each script one at a time in sequence (rather than as a whole batch), which is why they're individual promises vs promiseAll
const scripts = [
"https://somewhere.com/scripts/script1.js",
"https://somewhere.com/scripts/script2.js",
"https://somewhere.com/scripts/script3.js",
"https://somewhere.com/scripts/script4.js"
];
let sctr = 0;
const doScript = () => {
if (sctr++ >= scripts.length) {
console.log('all scripts loaded');
Template.render();
return;
}
const scriptPromise = new Promise((resolve, reject) => {
const script = document.createElement('script');
document.body.appendChild(script);
script.onload = resolve;
script.onerror = reject;
script.async = true; // they're loading one at a time, so async is ok
script.src = scripts[sctr];
});
scriptPromise.then(() => {
console.log(scripts[sctr], 'loaded');
doScript();
},
(err) => {
console.error(scripts[sctr], 'failed', err);
// halting
});
}

Time limit for code execution

Considering the following execution of a JS program in a JS settlement, how can I implement a time limit to prevent infinite loops in program?
try {
var x = eval(document.getElementById("program").value);
} catch(e) {
...
}
Note: the call should be able to specify a maximum execution time, just in case program enters an infinite loop.
You could use a webworker to run this in another thread:
// Worker-helper.js
self.onmessage = function(e) {
self.postMessage(eval(e.data));
};
Them use the worker as:
var worker = new Worker('Worker-helper.js');
worker.postMessage(document.getElementById("program").value);
worker.onmessage = result => {
alert(result);
};
setTimeout(() => worker.terminate(), 10 * 1000);
...which will kill the worker after 10 seconds.
Easy to use utility:
function funToWorker(fn) {
var response = "(" + fn.toString() + ")()";
var blob;
try {
blob = new Blob([response], {type: 'application/javascript'});
} catch (e) { // Backwards-compatibility
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
blob = new BlobBuilder();
blob.append(response);
blob = blob.getBlob();
}
return new Worker(URL.createObjectURL(blob));
}
function limitedEval(string, timeout) {
return new Promise((resolve, reject) => {
const worker = funToWorker(function() {
self.onmessage = e => {
self.postMessage(eval(e.data));
}
});
worker.onmessage = e => resolve(e.data);
worker.postMessage(string);
setTimeout(() => {
worker.terminate();
reject();
}, timeout);
});
}
Usable as:
limitedEval("1 + 2", 1000)
.then(console.log)
.catch(console.error);
Try it!

Polyfill for onload of script tag in <head>

I'm trying to lazy load javascript into the head with this function:
function bfLoadScripts(strPath) {
var r = false;
var scriptTag = document.createElement('script');
scriptTag.src = strPath;
scriptTag.type = 'text/javascript';
scriptTag.addEventListener('load',function() {
//alert('JS loaded');
r = true;
});
var headTag = document.getElementsByTagName('head')[0];
headTag.appendChild(scriptTag);
}
It works in FF (latest), Chrome (latest), IE 11 but not on Safari iOS 5.1 and Safari PC.
I tried this before but it's also not supported in Safari:
scriptTag.onload = scriptTag.onreadystatechange = function() {
if ( !r && (!this.readyState || this.readyState == 'complete') ) {
r = true;
};
Is there a polyfill for the "onload" event? Or asked differently: Is there a total different way of doing this? Without overloading the whole process by using libraries, plugins etc.
This is my version, which works in IE 8 (TypeScript):
type IE8ScriptReadyState = "loading" | "loaded";
interface IE8CompatibleScriptElement extends HTMLScriptElement
{
onreadystatechange: ()=> void;
readyState: IE8ScriptReadyState;
}
export function loadPolyfill(src): Promise<void>
{
return new Promise(function (resolve, reject)
{
let js = <IE8CompatibleScriptElement>document.createElement('script');
js.src = src;
if (!('onload' in js))
{
// #ts-ignore
js.onreadystatechange = function ()
{
if (js.readyState === 'loaded')
{
js.onreadystatechange = null;
resolve();
};
};
}
js.onload = function ()
{
js.onload = null;
resolve();
};
js.onerror = function ()
{
js.onerror = null;
reject(new Error('Failed to load script ' + src));
};
js.oncancel = function ()
{
js.oncancel = null;
reject(new Error('Cancelled loading of script ' + src));
};
if (document.head)
document.head.appendChild(js);
else
document.getElementsByTagName('head')[0].appendChild(js);
});
}
// loadScript("foo", function () { alert("hi"); });
// loadScript("/ts/myimport.js", function () { alert("hi"); });
function loadScript(src: string, done: (err?: Error) => void)
{
console.log(src);
let js = <IE8CompatibleScriptElement>document.createElement('script');
js.src = src;
if (!('onload' in js))
{
// #ts-ignore
js.onreadystatechange = function ()
{
if (js.readyState === 'loaded')
{
js.onreadystatechange = null;
if (done != null)
done();
};
};
}
js.onload = function ()
{
js.onload = null;
console.log("onload " + src);
if(done != null)
done();
};
js.onerror = function ()
{
js.onerror = null;
console.log("onerror " + src);
if (done != null)
done(new Error('Failed to load script ' + src));
};
//js.onloadend = function ()
//{
// alert("onerror");
// done(new Error('Failed to load script ' + src));
//};
js.oncancel = function ()
{
js.oncancel = null;
console.log("oncancel " + src);
if (done != null)
done(new Error('Cancelled loading of script ' + src));
};
if (document.head)
document.head.appendChild(js);
else
document.getElementsByTagName('head')[0].appendChild(js);
}

Wait until a condition is true?

I'm using navigator.geolocation.watchPosition in JavaScript, and I want a way to deal with the possibility that the user might submit a form relying on location before watchPosition has found its location.
Ideally the user would see a 'Waiting for location' message periodically until the location was obtained, then the form would submit.
However, I'm not sure how to implement this in JavaScript given its lack of a wait function.
Current code:
var current_latlng = null;
function gpsSuccess(pos){
//console.log('gpsSuccess');
if (pos.coords) {
lat = pos.coords.latitude;
lng = pos.coords.longitude;
}
else {
lat = pos.latitude;
lng = pos.longitude;
}
current_latlng = new google.maps.LatLng(lat, lng);
}
watchId = navigator.geolocation.watchPosition(gpsSuccess,
gpsFail, {timeout:5000, maximumAge: 300000});
$('#route-form').submit(function(event) {
// User submits form, we need their location...
while(current_location==null) {
toastMessage('Waiting for your location...');
wait(500); // What should I use instead?
}
// Continue with location found...
});
Modern solution using Promise
function waitFor(conditionFunction) {
const poll = resolve => {
if(conditionFunction()) resolve();
else setTimeout(_ => poll(resolve), 400);
}
return new Promise(poll);
}
Usage
waitFor(_ => flag === true)
.then(_ => console.log('the wait is over!'));
or
async function demo() {
await waitFor(_ => flag === true);
console.log('the wait is over!');
}
References
Promises
Arrow Functions
Async/Await
Personally, I use a waitfor() function which encapsulates a setTimeout():
//**********************************************************************
// function waitfor - Wait until a condition is met
//
// Needed parameters:
// test: function that returns a value
// expectedValue: the value of the test function we are waiting for
// msec: delay between the calls to test
// callback: function to execute when the condition is met
// Parameters for debugging:
// count: used to count the loops
// source: a string to specify an ID, a message, etc
//**********************************************************************
function waitfor(test, expectedValue, msec, count, source, callback) {
// Check if condition met. If not, re-check later (msec).
while (test() !== expectedValue) {
count++;
setTimeout(function() {
waitfor(test, expectedValue, msec, count, source, callback);
}, msec);
return;
}
// Condition finally met. callback() can be executed.
console.log(source + ': ' + test() + ', expected: ' + expectedValue + ', ' + count + ' loops.');
callback();
}
I use my waitfor() function in the following way:
var _TIMEOUT = 50; // waitfor test rate [msec]
var bBusy = true; // Busy flag (will be changed somewhere else in the code)
...
// Test a flag
function _isBusy() {
return bBusy;
}
...
// Wait until idle (busy must be false)
waitfor(_isBusy, false, _TIMEOUT, 0, 'play->busy false', function() {
alert('The show can resume !');
});
This is precisely what promises were invented and implemented (since OP asked his question) for.
See all of the various implementations, eg promisejs.org
You'll want to use setTimeout:
function checkAndSubmit(form) {
var location = getLocation();
if (!location) {
setTimeout(checkAndSubmit, 500, form); // setTimeout(func, timeMS, params...)
} else {
// Set location on form here if it isn't in getLocation()
form.submit();
}
}
... where getLocation looks up your location.
You could use a timeout to try to re-submit the form:
$('#route-form').submit(function(event) {
// User submits form, we need their location...
if(current_location==null) {
toastMessage('Waiting for your location...');
setTimeout(function(){ $('#route-form').submit(); }, 500); // Try to submit form after timeout
return false;
} else {
// Continue with location found...
}
});
export default (condition: Function, interval = 1000) =>
new Promise((resolve) => {
const runner = () => {
const timeout = setTimeout(runner, interval);
if (condition()) {
clearTimeout(timeout);
resolve(undefined);
return;
}
};
runner();
});
class App extends React.Component {
componentDidMount() {
this.processToken();
}
processToken = () => {
try {
const params = querySearch(this.props.location.search);
if('accessToken' in params){
this.setOrderContext(params);
this.props.history.push(`/myinfo`);
}
} catch(ex) {
console.log(ex);
}
}
setOrderContext (params){
//this action calls a reducer and put the token in session storage
this.props.userActions.processUserToken({data: {accessToken:params.accessToken}});
}
render() {
return (
<Switch>
//myinfo component needs accessToken to retrieve my info
<Route path="/myInfo" component={InofUI.App} />
</Switch>
);
}
And then inside InofUI.App
componentDidMount() {
this.retrieveMyInfo();
}
retrieveMyInfo = async () => {
await this.untilTokenIsSet();
const { location, history } = this.props;
this.props.processUser(location, history);
}
untilTokenIsSet= () => {
const poll = (resolve) => {
const { user } = this.props;
const { accessToken } = user;
console.log('getting accessToken', accessToken);
if (accessToken) {
resolve();
} else {
console.log('wating for token .. ');
setTimeout(() => poll(resolve), 100);
}
};
return new Promise(poll);
}
Try using setInterval and clearInterval like this...
var current_latlng = null;
function gpsSuccess(pos) {
//console.log('gpsSuccess');
if (pos.coords) {
lat = pos.coords.latitude;
lng = pos.coords.longitude;
} else {
lat = pos.latitude;
lng = pos.longitude;
}
current_latlng = new google.maps.LatLng(lat, lng);
}
watchId = navigator.geolocation.watchPosition(gpsSuccess,
gpsFail, {
timeout: 5000,
maximumAge: 300000
});
$('#route-form').submit(function (event) {
// User submits form, we need their location...
// Checks status every half-second
var watch = setInterval(task, 500)
function task() {
if (current_latlng != null) {
clearInterval(watch)
watch = false
return callback()
} else {
toastMessage('Waiting for your location...');
}
}
function callback() {
// Continue on with location found...
}
});
This accepts any function, even if it's async, and when it evaluates to a truthy value (checking every quarter-second by default), resolves to it.
function waitFor(condition, step = 250, timeout = Infinity) {
return new Promise((resolve, reject) => {
const now = Date.now();
let running = false;
const interval = setInterval(async () => {
if (running) return;
running = true;
const result = await condition();
if (result) {
clearInterval(interval);
resolve(result);
} else if (Date.now() - now >= timeout * 1000) {
clearInterval(interval);
reject(result);
}
running = false;
}, step);
});
}
Example
example();
async function example() {
let foo = 'bar';
setTimeout(() => foo = null, 2000);
console.log(`foo === "${foo}"`);
await waitFor(() => foo === null);
console.log('2 seconds have elapsed.');
console.log(`foo === ${foo}`);
}
function waitFor(condition, step = 250, timeout = Infinity) {
return new Promise((resolve, reject) => {
const now = Date.now();
let running = false;
const interval = setInterval(async () => {
if (running) return;
running = true;
const result = await condition();
if (result) {
clearInterval(interval);
resolve(result);
} else if (Date.now() - now >= timeout * 1000) {
clearInterval(interval);
reject(result);
}
running = false;
}, step);
});
}

Categories