Best way to asynchronously load external js files via Promises - javascript

I am building a website that uses some external js files. I load the files via the code below, but I am not sure how to proceed if one or more of the files fails downloading. Should I just keep requesting them until all of them download? Is it better to do a separate onload event for each file? How would I know which file has failed loading and needs to be requested again?
var filesToLoad = ["https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"];
var loader = new ScriptLoader();
filesToLoad.forEach(function(file) {
loader.add(file);
});
loader.loaded(function(failedCallbackF) {
console.log("Error.");
//Try getting the files again??
});
function ScriptLoader() {
var promises = [];
this.add = function(url) {
var promise = new Promise(function(resolve, reject) {
var script = document.createElement('script');
script.src = url;
script.addEventListener('load', function() {
resolve(script);
}, false);
script.addEventListener('error', function() {
reject(script);
console.log('was rej');
}, false);
document.body.appendChild(script);
});
promises.push(promise);
};
this.loaded = function(callbackOnFailed) {
Promise.all(promises).then(function(result1) {
console.log('Script loaded from:', result1);
}, callbackOnFailed);
};
}

Well, there is an official API for it called "dynamic import", I recommend that you use it or shim it (with something like SystemJS or with a tool that supports it like webpack).
import("yourScriptFile.js").then(function(){
// script loaded.
});
If you want to load multiple files you can also use it:
Promise.all(["url1", "url2"].map(System.import)).then(function(){
// loaded all here
});

May be this will help you.
var filesToLoad = ["https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"];
filesToLoad.forEach(function(file) {
var loader = new ScriptLoader();
loader.add(file)
loader.loaded(function(failedCallbackF) {
console.log("Error.");
//reload this file
});
});
function ScriptLoader() {
var promises = [];
this.add = function(url) {
var promise = new Promise(function(resolve, reject) {
var script = document.createElement('script');
script.src = url;
script.addEventListener('load', function() {
resolve(script);
}, false);
script.addEventListener('error', function() {
reject(script);
console.log('was rej');
}, false);
document.body.appendChild(script);
});
promises.push(promise);
};
this.loaded = function(callbackOnFailed) {
Promise.all(promises).then(function(result1) {
console.log('Script loaded from:', result1);
}, callbackOnFailed);
};
}

Related

Trouble with running function before FileReader

I've got some code running within a Vue.js component, but I get the following error:
TypeError: Cannot read property 'then' of undefined
I just want to run loadingAnimation() before the filereading, but searching on the internet for this error didn't really help me. What is the best way of doing this?
This is my code:
openFileBrowse() {
var vm = this;
var input = document.getElementById("filebutton");
if (input.files[0].type != "application/vnd.ms-excel"){
alert("You have uploaded a wrong file type. We require a .csv file not a " + input.files[0].type + " file.");
} else {
//Update loader text
vm.updateLoader('parsing csv');
//Start loadscreen
vm.loadingAnimation().then(function () {
//Start reading data
var reader = new FileReader();
var csvData = "";
var jsonData;
var iconv = require('iconv-lite');
reader.onload = function(){
csvData = iconv.decode(reader.result, 'latin1');
jsonData = vm.tsvJSON(csvData);
vm.addFiles(jsonData);
};
reader.onloadend = function(){
//Go to visualization page
router.push({ name: 'Visualization' });
};
reader.readAsText(input.files[0]);
});
}
},
loadingAnimation() {
//Make loading screen visible with animation
var target = document.getElementById('loadBox');
target.classList.remove('hidden')
setTimeout(function () {
target.classList.remove('visuallyhidden');
}, 20);
}
loadingAnimation isn't an async function and doesn't explicitly return a promise, so calling it doesn't give you a promise. In fact, it doesn't have any explicit return value, so calling it will always give you undefined.
If you want it to return a promise that will be fulfilled when the timer callback is fired, you have to code it. For instance:
loadingAnimation() {
return new Promise(resolve => {
//Make loading screen visible with animation
var target = document.getElementById('loadBox');
target.classList.remove('hidden')
setTimeout(function () {
target.classList.remove('visuallyhidden');
resolve();
}, 20);
});
}

Issue with syntax for javascript promise

I am using pdfjs to read a pdf file and get its pages as images. After all the images are loaded, I need to call an ajax to send and get details from the server. The code for iterating pages in the pdf has been taken from here: https://ourcodeworld.com/articles/read/405/how-to-convert-pdf-to-text-extract-text-from-pdf-with-javascript
I am having problem writing the syntax for the promise that will call the ajax function after all the required details mentioned above has been fetched.
Here is my code:
getDataUrlsAndSizesFromPdf(file).then(proceedAndCheckOnServer(file));
const getDataUrlsAndSizesFromPdf = function(file) {
PDFJS.disableWorker = true;
fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
return new Promise(function(resolve, reject) {
fileReader.onload = function(ev) {
PDFJS.getDocument(fileReader.result).then(function (pdf) {
var pdfDocument = pdf;
var pagesPromises = [];
for (var i = 0; i < pdf.pdfInfo.numPages; i++) {
var pageNum = i + 1;
pagesPromises.push(getImageUrl(pageNum, pdfDocument));
}
Promise.all(pagesPromises).then(function () {
console.log(pdfPagesInfo);
resolve();
}, function () {
console.log('failed');
reject();
});
}, function (reason) {
console.error(reason);
});
}
});
}
function getImageUrl() {
return new Promise(function (resolve, reject) {
PDFDocumentInstance.getPage(pageNum).then(function (pdfPage) {
var scale = 1;
var viewport = pdfPage.getViewport(scale);
var canvas = document.getElementById('dummy-canvas');
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
var task = pdfPage.render({canvasContext: context, viewport: viewport})
task.promise.then(function(){
var sizesArr = {
height : viewport.height,
width : viewport.width
}
pdfPagesInfo.sizes[pageNum.toString()] = sizesArr
pdfPagesInfo.images[pageNum.toString()] = canvas.toDataURL('image/jpeg');
resolve();
});
});
});
}
function proceedAndCheckOnServer() {
....
}
What I want is "proceedAndCheckOnServer()" to be executed when all the details have been fetched from "getImageUrl()". But presently the execution directly goes to "proceedAndCheckOnServer()" without waiting for the promise from "getDataUrlsAndSizesFromPdf" to be resolved. I am new to javascript promises. Please help me with the syntax.
You are calling your function instead of using a callback function.
proceedAndCheckOnServer is invoked, and the result of that function is used as the parameter to then.
getDataUrlsAndSizesFromPdf(file).then(proceedAndCheckOnServer(file));
Try one of these:
getDataUrlsAndSizesFromPdf(file).then(()=>proceedAndCheckOnServer(file));
getDataUrlsAndSizesFromPdf(file).then(function(){ proceedAndCheckOnServer(file) });
Or resolve your getDataUrlsAndSizesFromPdf promise with file and use the function without () to pipeline the result.
getDataUrlsAndSizesFromPdf(file).then(proceedAndCheckOnServer);

Function return from XMLHttpRequest [duplicate]

I'm trying to load JS scripts dynamically, but using jQuery is not an option.
I checked jQuery source to see how getScript was implemented so that I could use that approach to load scripts using native JS. However, getScript only calls jQuery.get()
and I haven't been able to find where the get method is implemented.
So my question is,
What's a reliable way to implement my own getScript method using native JavaScript?
Thanks!
Here's a jQuery getScript alternative with callback functionality:
function getScript(source, callback) {
var script = document.createElement('script');
var prior = document.getElementsByTagName('script')[0];
script.async = 1;
script.onload = script.onreadystatechange = function( _, isAbort ) {
if(isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) {
script.onload = script.onreadystatechange = null;
script = undefined;
if(!isAbort && callback) setTimeout(callback, 0);
}
};
script.src = source;
prior.parentNode.insertBefore(script, prior);
}
You can fetch scripts like this:
(function(document, tag) {
var scriptTag = document.createElement(tag), // create a script tag
firstScriptTag = document.getElementsByTagName(tag)[0]; // find the first script tag in the document
scriptTag.src = 'your-script.js'; // set the source of the script to your script
firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag); // append the script to the DOM
}(document, 'script'));
use this
var js_script = document.createElement('script');
js_script.type = "text/javascript";
js_script.src = "http://www.example.com/script.js";
js_script.async = true;
document.getElementsByTagName('head')[0].appendChild(js_script);
Firstly, Thanks for #Mahn's answer. I rewrote his solution in ES6 and promise, in case someone need it, I will just paste my code here:
const loadScript = (source, beforeEl, async = true, defer = true) => {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
const prior = beforeEl || document.getElementsByTagName('script')[0];
script.async = async;
script.defer = defer;
function onloadHander(_, isAbort) {
if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = null;
script.onreadystatechange = null;
script = undefined;
if (isAbort) { reject(); } else { resolve(); }
}
}
script.onload = onloadHander;
script.onreadystatechange = onloadHander;
script.src = source;
prior.parentNode.insertBefore(script, prior);
});
}
Usage:
const scriptUrl = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoad&render=explicit';
loadScript(scriptUrl).then(() => {
console.log('script loaded');
}, () => {
console.log('fail to load script');
});
and code is eslinted.
This polishes up previous ES6 solutions and will work in all modern browsers
Load and Get Script as a Promise
const getScript = url => new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.async = true
script.onerror = reject
script.onload = script.onreadystatechange = function() {
const loadState = this.readyState
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
script.onload = script.onreadystatechange = null
resolve()
}
document.head.appendChild(script)
})
Usage
getScript('https://dummyjs.com/js')
.then(() => {
console.log('Loaded', dummy.text())
})
.catch(() => {
console.error('Could not load script')
})
Also works for JSONP endpoints
const callbackName = `_${Date.now()}`
getScript('http://example.com/jsonp?callback=' + callbackName)
.then(() => {
const data = window[callbackName];
console.log('Loaded', data)
})
Also, please be careful with some of the AJAX solutions listed as they are bound to the CORS policy in modern browsers https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
There are some good solutions here but many are outdated. There is a good one by #Mahn but as stated in a comment it is not exactly a replacement for $.getScript() as the callback does not receive data. I had already written my own function for a replacement for $.get() and landed here when I need it to work for a script. I was able to use #Mahn's solution and modify it a bit along with my current $.get() replacement and come up with something that works well and is simple to implement.
function pullScript(url, callback){
pull(url, function loadReturn(data, status, xhr){
//If call returned with a good status
if(status == 200){
var script = document.createElement('script');
//Instead of setting .src set .innerHTML
script.innerHTML = data;
document.querySelector('head').appendChild(script);
}
if(typeof callback != 'undefined'){
//If callback was given skip an execution frame and run callback passing relevant arguments
setTimeout(function runCallback(){callback(data, status, xhr)}, 0);
}
});
}
function pull(url, callback, method = 'GET', async = true) {
//Make sure we have a good method to run
method = method.toUpperCase();
if(!(method === 'GET' || method === 'POST' || method === 'HEAD')){
throw new Error('method must either be GET, POST, or HEAD');
}
//Setup our request
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) { // XMLHttpRequest.DONE == 4
//Once the request has completed fire the callback with relevant arguments
//you should handle in your callback if it was successful or not
callback(xhr.responseText, xhr.status, xhr);
}
};
//Open and send request
xhr.open(method, url, async);
xhr.send();
}
Now we have a replacement for $.get() and $.getScript() that work just as simply:
pullScript(file1, function(data, status, xhr){
console.log(data);
console.log(status);
console.log(xhr);
});
pullScript(file2);
pull(file3, function loadReturn(data, status){
if(status == 200){
document.querySelector('#content').innerHTML = data;
}
}
Mozilla Developer Network provides an example that works asynchronously and does not use 'onreadystatechange' (from #ShaneX's answer) that is not really present in a HTMLScriptTag:
function loadError(oError) {
throw new URIError("The script " + oError.target.src + " didn't load correctly.");
}
function prefixScript(url, onloadFunction) {
var newScript = document.createElement("script");
newScript.onerror = loadError;
if (onloadFunction) { newScript.onload = onloadFunction; }
document.currentScript.parentNode.insertBefore(newScript, document.currentScript);
newScript.src = url;
}
Sample usage:
prefixScript("myScript1.js");
prefixScript("myScript2.js", function () { alert("The script \"myScript2.js\" has been correctly loaded."); });
But #Agamemnus' comment should be considered: The script might not be fully loaded when onloadFunction is called. A timer could be used setTimeout(func, 0) to let the event loop finalize the added script to the document. The event loop finally calls the function behind the timer and the script should be ready to use at this point.
However, maybe one should consider returning a Promise instead of providing two functions for exception & success handling, that would be the ES6 way. This would also render the need for a timer unnecessary, because Promises are handled by the event loop - becuase by the time the Promise is handled, the script was already finalized by the event loop.
Implementing Mozilla's method including Promises, the final code looks like this:
function loadScript(url)
{
return new Promise(function(resolve, reject)
{
let newScript = document.createElement("script");
newScript.onerror = reject;
newScript.onload = resolve;
document.currentScript.parentNode.insertBefore(newScript, document.currentScript);
newScript.src = url;
});
}
loadScript("test.js").then(() => { FunctionFromExportedScript(); }).catch(() => { console.log("rejected!"); });
window.addEventListener('DOMContentLoaded',
function() {
var head = document.getElementsByTagName('HEAD')[0];
var script = document.createElement('script');
script.src = "/Content/index.js";
head.appendChild(script);
});
Here's a version that preserves the accept and x-requested-with headers, like jquery getScript:
function pullScript(url, callback){
pull(url, function loadReturn(data, status, xhr){
if(status === 200){
var script = document.createElement('script');
script.innerHTML = data; // Instead of setting .src set .innerHTML
document.querySelector('head').appendChild(script);
}
if (typeof callback != 'undefined'){
// If callback was given skip an execution frame and run callback passing relevant arguments
setTimeout(function runCallback(){callback(data, status, xhr)}, 0);
}
});
}
function pull(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
callback(xhr.responseText, xhr.status, xhr);
}
};
xhr.open('GET', url, true);
xhr.setRequestHeader('accept', '*/*;q=0.5, text/javascript, application/javascript, application/ecmascript, application/x-ecmascript');
xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest');
xhr.send();
}
pullScript(URL);

I wrote some killer Javascript. How can I make it easily reusable?

I have been playing around with Chromes filestorage API. I have built a couple of functions that together automatically downloads a json-object and stores it as a string. If the last server-request was done within 24 hours. I automatically use the last version of the file. I use this for managing a huge data-dump that I do statistical analysis on.
The entire system only has one function that needs to be exposed. It's getData.
Currently all these functions are global variables. How should I make this contained in an orderly way.
//This file will cache serverdata every day.
var onInitFs,
errorHandler,
fileSystemInit,
saveFile,
readFile,
fileSystem,
getData;
//request rights to save files to system.
fileSystemInit = function(){
//Browser specific
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
navigator.webkitPersistentStorage.requestQuota(1048*1048*256, function(grantedBytes) {
//once approved (or if previously approved):
window.requestFileSystem(PERSISTENT, grantedBytes, onInitFs, errorHandler);
}, function(e) {
console.log('Error', e);
});
};
//make filesystem global.
onInitFs = function(fs) {
fileSystem = fs;
};
fileSystemInit();
saveFile = function(url, content, callback){
var filename = makeFilename(url)
if(!fileSystem){
console.log('no filesystem registered')
return;
}
fileSystem.root.getFile(filename, {create: true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
var blob = new Blob([JSON.stringify(content)], {type: 'application/json'});
fileWriter.write(blob);
fileWriter.onwriteend = function(e) {
console.debug('Write completed.', e);
if(callback){
callback();
}
};
fileWriter.onerror = function(e) {
console.log('Write failed: ', e);
};
}, errorHandler);
}, errorHandler);
};
readFile = function(url, callback){
var filename = makeFilename(url)
if(!fileSystem){
console.log('no filesystem registered');
return;
}
fileSystem.root.getFile(filename, {}, function(fileEntry){
//this object reads files.
var reader = new FileReader();
//register callback for read files
reader.onloadend = function(e){
var callbackValue = JSON.parse(this.result)
callback(callbackValue);
};
//read file-function
fileEntry.file(function(file){
reader.readAsText(file);
},errorHandler);
},errorHandler);
};
makeFilename = function(url){
return url.replace(/\W/g, '') +'.json'
}
errorHandler = function(e) {
console.log('Error: ', e);
};
getData = function(url, callbackNewData, callbackOldData){
var lastDownloaded = localStorage.getItem(url+'lastDownloaded'),
oneDay = 1000*60*60*24;
//update data if the data is old.
window.setTimeout(function(){
if(!lastDownloaded || new Date()-new Date(lastDownloaded) > oneDay ){
console.debug('downloading '+url);
d3.json(url, function(data){
localStorage.setItem(url+'lastDownloaded',new Date());
console.debug('saving '+url);
saveFile(url, data, function(){
callbackNewData(url);
});
});
}else{
callbackOldData(url);
}
}, 200);
};
You can wrap the whole thing in an anonymous function and expose getData only. This is the easiest way to do.
var getDataFromUrl = function () {
//This file will cache serverdata every day.
var onInitFs,
errorHandler,
fileSystemInit,
saveFile,
readFile,
fileSystem,
getData;
// Your original code here ...
return getData; // This exposes the getData function.
})();
In this way you only exposes one global function getDataFromUrl, which is exactly the public API.
For more modern usage, you may want to check out Common JS Modules and Browserify, which let you do exports and require both in browser and NodeJS. There is also a UMD Pattern for exporting libraries.
(function(window){
'use strict'
var onInitFs,errorHandler,fileSystemInit,saveFile,readFile,fileSystem,getData;
fileSystemInit = function(){
// your code
};
//make filesystem global.
onInitFs = function(fs) {
//your code
};
fileSystemInit();
saveFile = function(url, content, callback){
//your code
};
readFile = function(url, callback){
//your code
};
makeFilename = function(url){
//your code
}
errorHandler = function(e) {
//your code
};
getData = function(url, callbackNewData, callbackOldData){
//your code
};
window.HimmatorsFileStorageAPI = getData; // you can change the name here
})(window);
And you can use it simply by including this script in your page and then calling
HimmatorsFileStorageAPI(url, callbackNewData, callbackOldData);
The Module pattern would be a good start: http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript
Pseudo-class also would be great:
var StorageInterface = function(arg1, arg2){
this.arg1 = arg1;
this.arg2 = arg2;
}
StorageInterface.prototype.method = function(arg3, arg4){
return arg3 + arg4 + this.arg1 + this.arg2;
}
var si = new StorageInterface(100, 500);
si.method(3, 4);
Just prototype the heck out of it :-) And use some scoped instances(using var that = this) to pass elements back to the parent objects from different scopes.
Now you can just start a new FileSystemInstance() to do your magic.
If you wish to make the more "arcane" methods private you could consider moving them to a object within your object and such, but in the end anyone with true perserverance will be able to access them. So I advice to go with a public way, and name the private methods _fileSystemInit, so people who read the code know it's an internalised method.
//This file will cache serverdata every day.
function FileSystemInstance() {
this.fileSystem = null;
this.requestFileSystem = null;
this.fileSystemInit();
}
//request rights to save files to system.
FileSystemInstance.prototype.fileSystemInit = function(){
//Browser specific
this.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
this.requestFileSystem = this.requestFileSystem.bind(window);
console.log(this.requestFileSystem);
var that = this;
console.log(that.requestFileSystem);
navigator.webkitPersistentStorage.requestQuota(1048*1048*256, function(grantedBytes) {
//once approved (or if previously approved):
console.log(that.requestFileSystem);
that.requestFileSystem(PERSISTENT, grantedBytes, function(fs){that.onInitFs(fs)}, function(e){that.errorHandler(e)});
}, function(e) {
console.log('Error', e);
});
};
//make filesystem global.
FileSystemInstance.prototype.onInitFs = function(fs) {
this.fileSystem = fs;
};
FileSystemInstance.prototype.saveFile = function(url, content, callback){
var filename = this.makeFilename(url)
if(!fileSystem){
console.log('no filesystem registered')
return;
}
this.fileSystem.root.getFile(filename, {create: true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
var blob = new Blob([JSON.stringify(content)], {type: 'application/json'});
fileWriter.write(blob);
fileWriter.onwriteend = function(e) {
console.debug('Write completed.', e);
if(callback){
callback();
}
};
fileWriter.onerror = function(e) {
console.log('Write failed: ', e);
};
}, errorHandler);
}, errorHandler);
};
FileSystemInstance.prototype.readFile = function(url, callback){
var filename = this.makeFilename(url)
if(!this.fileSystem){
throw new Error('no filesystem registered');
}
this.fileSystem.root.getFile(filename, {}, function(fileEntry){
//this object reads files.
var reader = new FileReader();
//register callback for read files
reader.onloadend = function(e){
var callbackValue = JSON.parse(this.result)
callback(callbackValue);
};
//read file-function
fileEntry.file(function(file){
reader.readAsText(file);
},errorHandler);
},errorHandler);
};
FileSystemInstance.prototype.makeFilename = function(url){
return url.replace(/\W/g, '') +'.json'
}
FileSystemInstance.prototype.errorHandler = function(e) {
console.error('Error: ', e);
};
FileSystemInstance.prototype.getData = function(url, callbackNewData, callbackOldData){
var that = this;
var lastDownloaded = localStorage.getItem(url+'lastDownloaded'),
oneDay = 1000*60*60*24;
//update data if the data is old.
window.setTimeout(function(){
if(!lastDownloaded || new Date()-new Date(lastDownloaded) > oneDay ){
console.debug('downloading '+url);
d3.json(url, function(data){
localStorage.setItem(url+'lastDownloaded',new Date());
console.debug('saving '+url);
that.saveFile(url, data, function(){
callbackNewData(url);
});
});
}else{
callbackOldData(url);
}
}, 200);
};
FileSystem = new FileSystemInstance();
var data = FileSystem.getData();
console.log("Data is: ",data);

Asynchronous Script Loading Callback

http://jsfiddle.net/JamesKyle/HQDu6/
I've created a short function based on Mathias Bynens Optimization of the Google Analytics asynchronous script that goes as following:
function async(src) {
var d = document, t = 'script',
o = d.createElement(t),
s = d.getElementsByTagName(t)[0];
o.src = '//' + src;
s.parentNode.insertBefore(o, s);
}
This works great and I've already started using it for several different scripts
// Crazy Egg
async('dnn506yrbagrg.cloudfront.net/pages/scripts/XXXXX/XXXXX.js?' + Math.floor(new Date().getTime() / 3600000));
// User Voice
var uvOptions = {};
async('widget.uservoice.com/XXXXX.js');
// Google Analytics
var _gaq = [['_setAccount', 'UA-XXXXX-XX'], ['_setDomainName', 'coachup.com'], ['_trackPageview']];
async('google-analytics.com/ga.js');
// Stripe
async('js.stripe.com/v1');​
The problem comes when I encounter a script that needs to be called after it's loaded:
// Snap Engage
async('snapabug.appspot.com/snapabug.js');
SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
So I figured I'd turn this into a callback function that would be used as so:
async('snapabug.appspot.com/snapabug.js', function() {
SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
});
I did not expect that this would be difficult for me to do but it has turned out that way.
My question is what is the most efficient way to add a callback without overcomplicating the code.
See the jsfiddle: http://jsfiddle.net/JamesKyle/HQDu6/
Thanks RASG for https://stackoverflow.com/a/3211647/982924
Async function with callback:
function async(u, c) {
var d = document, t = 'script',
o = d.createElement(t),
s = d.getElementsByTagName(t)[0];
o.src = '//' + u;
if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
s.parentNode.insertBefore(o, s);
}
Usage:
async('snapabug.appspot.com/snapabug.js', function() {
SnapABug.init('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
});
jsFiddle
A more recent snippet:
async function loadAsync(src) {
const script = document.createElement('script');
script.src = src;
return new Promise((resolve, reject) => {
script.onreadystatechange = function () {
if (script.readyState === 'loaded' || script.readyState === 'complete') {
script.onreadystatechange = null;
resolve(true);
}
};
document.getElementsByTagName('head')[0].appendChild(script);
});
}
utilisation
loadAsync(`https://....js`).then(_ => {
// ... script loaded here
})
James Kyle's answer doesn't take IE9 into account. Here is a modified version of the code I found in the link proposed in the comments. Modify the var baseUrl so it can find the script accordingly.
//for requiring a script loaded asynchronously.
function loadAsync(src, callback, relative){
var baseUrl = "/resources/script/";
var script = document.createElement('script');
if(relative === true){
script.src = baseUrl + src;
}else{
script.src = src;
}
if(callback !== null){
if (script.readyState) { // IE, incl. IE9
script.onreadystatechange = function() {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
callback();
}
};
} else {
script.onload = function() { // Other browsers
callback();
};
}
}
document.getElementsByTagName('head')[0].appendChild(script);
}
utilisation:
loadAsync('https://www.gstatic.com/charts/loader.js' , function(){
chart.loadCharts();
});
// OR relative path
loadAsync('fastclick.js', null, true);
The other answers works well, but aren't super readable or require Promises. Here is my two cents:
function loadScript(src, callback) {
var script = document.createElement('script');
script.setAttribute('src', src);
script.addEventListener('load', callback);
document.head.appendChild(script);
},

Categories