Load async resource with requirejs timeout - javascript

I tried to load the Google APIs Client Library for JavaScript with requirejs and the async plugin:
require.config({
paths : {
async : '../lib/requirejs/async'
},
waitSeconds: 60
});
define('gapi', ['async!https://apis.google.com/js/client.js!callback'],
function(){
console.log('gapi loaded');
return gapi.client;
}
);
require(['gapi'], function(){
console.log("Callback");
console.log(gapi);
});
The usual way to load this library is
<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
Everything is loaded in less than 2s but I always get this error:
Uncaught Error: Load timeout for modules: async!https://apis.google.com/js/client.js!callback_unnormalized2,async!https://apis.google.com/js/client.js!callback
http://requirejs.org/docs/errors.html#timeout

TL;DR; change the !callback to !onload that should fix the timeout.
define('gapi', ['async!https://apis.google.com/js/client.js!onload'],
function(){
console.log('gapi loaded');
return gapi.client;
}
);
The value after the ! is used as the argument name for the async callback, in this case the URI loaded will be something like https://apis.google.com/js/client.js?onload=__async_req_3__ where __async_req_3__ is a global variable (callback function) triggered as soon as the Google API is loaded (notifies the plugin that all dependencies are met).

Related

Loading Maps API with the jsapi loader is deprecated

Recently I see this message in the console logs:
Loading Maps API with the jsapi loader is deprecated.
This is the code which causes it:
connected: function() {
...
},
initComponent: function() {
var me = this;
google.load('maps', '3', {
other_params: 'key=YOUR_API_KEY',
callback : me.connected
});
...
}
Loading it statically this is not really an option for me, because callback=connected will call back to window.connected() instead of me.connected():
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=connected"></script>
What is the alternative to google.load(), while calling back into the local scope? The documentation at Loading the Maps JavaScript API only offers static loading.
When adding this function in the global scope:
window.connected = function() {
console.log("Maps API connected");
};
It shows that it is connected, long before the application even launched:
Maps API connected
Util.js:747 [V] the Application was launched.
So this probably is a non-issue. All I have to do is to call it afterender:
listeners: {
afterrender: function() {
appdata.items.panel.Maps.connected();
}
},

Cannot use javascript function after script loaded using jQuery

I'm trying to programmatically load a local javascript file - papaparse library, and then use one of its functions:
$.getScript("./Content/Scripts/papaparse.js", function () {
console.log("Papaparse loaded successfully");
Papa.parse(file, { skipEmptyLines: true, header: false, complete: completeCallback });
});
The script loaded successfully, but calling the parse method throws an error:
ReferenceError: Papa is not defined
Within papaparse library, Papa defined as follows:
(function (global) {
"use strict";
var Papa = {};
Papa.parse = someFunction;
.
.
global.Papa = Papa;
}
If that helps, this entire code is called from a typescript file.
What am I doing wrong?
As Castro pointed out in his answer here that according to offical documentation of Jquery's getScript
The callback of getScript method is fired once the script has been loaded but not necessarily executed.
That means when getScript's callback function is called then the target script is only being loaded in current page context not fully executed so you need to give some time to JavaScript engine to execute that script.
How could you give that time. Hmm one of the options is setTimeout/setInterval.
You could use setTimeout/setInterval right inside callback function of getScript.
Modified version of your code would look like :-
$.getScript("./Content/Scripts/papaparse.js", function () {
console.log("Papaparse loaded successfully");
function dealWithPapa() {
Papa.parse(file, { skipEmptyLines: true, header: false, complete: completeCallback });
}
//regularly check after 100ms whether Papa is loaded or not
var interval = setInterval(function() {
if(Papa !== undefined) {
//once we have reference to Papa clear this interval
clearInterval(interval);
dealWithPapa();
}
},100);
});
I hope that would clear your doubt.
According to https://api.jquery.com/jquery.getscript/:
The callback is fired once the script has been loaded but not necessarily executed.
You might need to use a setTimeout(300, function(){...}) to wait for it to execute.

RequireJS local error callback for timeout ignored

Any file that is required that does not respond for longer than the wait time breaks the application and does not enter ANY error callback I define.
require.config({
waitSeconds: 1,
catchError: {
define: true
}
});
require.onError = function() {
// Does not reach here
};
require(['http://localhost/remote-file-that-does-not-respond-for-more-than-config-wait-time.js'], function() {}, function() {
// Err callback never triggers
});
The app will crash and the console will log:
Uncaught Error: Load timeout for modules: remote-scripts!,http://localhost/remote-file-that-does-not-respond-for-more-than-config-wait-time.js http://requirejs.org/docs/errors.html#timeout
Whereas if there is a 404 response, the error callback works just fine:
require(['http://localhost/external-file-that-does-not-exist.js'], function() {}, function() {
// Err callback does trigger
});
Why is it that when the server does not respond it won't trigger the local error callback, or even reach the global error callback?
You should set enforceDefine: true. Otherwise, RequireJS may fail to detect some timeouts. Documentation here.

How to find if the chained asynchronous scripts has been loaded?

Here's the scenario. I am doing a $.getScript()function call to get a script in my javascript file. The script that I'm downloading from the $.getScript() tries to download some other scripts that it's dependent on. In my script I'm using done() to check if the script loaded completely or not. And if it did, then I try calling the function that's not on the script that I just loaded form $.getScript but the script that was loaded in it.
It's getting confusing so let me demonstrate with some code:-
//my script.js
$.getScript("http://myexternaljs.com/first.js").done(function(){
doSecond(); //<- this resides in second.js which is being called in first.js below
}
//first.js
(function(){
$.getScript("http://firstexternal.com/second.js");
}());
//second.js
function doSecond(){
return console.log("hello world");
}
The problem here is second.js takes a little time to download so I keep getting doSecond() is undefined error on my call to doSecond() on done().
I could use a timeout and check if second.js loaded or not but is there a better way to do this?
I'm open to any AMD loaders or Promises answers as well.
You can also use $.ajaxSuccess:
$(document).ajaxComplete(function(ev, jqXhr, options) {
// You could be as specific as you need to here.
if ( options.url.match('second.js') ) {
doSecond();
}
});
Alternatively, you could do this inside ajaxComplete:
$(document).ajaxComplete(function(ev, jqXhr, options) {
// You could simplify to
// doSecond && doSecond()
// if you can trust that it will always be a function
if ( doSecond && $.isFunction(doSecond) ) {
doSecond();
}
});
The facts:
You have first.js and within this script is an include for second.js
You need to make a call to doSecond() which is defined in second.js
You need to ensure doSecond() is available before you call it
You can't directly change first.js or second.js but you can have someone else change it
Possible solutions, ordered by best to worst
1) Request that second.js be removed from first.js. Call them separately so that you can nest them:
$.getScript("first.js").done(function(){
$.getScript("second.js").done(function(){
doSecond();
});
});
This is the best solution. There are alternatives to this that basically do he same thing in principle (e.g. other people's answers here). If first.js was including second.js synchronously or otherwise forcing load before continuing (e.g. option #3 below), you wouldn't be running up against this problem to begin with. Therefore first.js already must be structured to deal with second.js be *a*sync loaded, so there shouldn't be an issue with them removing it from the file and you calling it yourself.
But you mentioned that the location of second.js is defined in first.js so this somehow isn't feasible to you (why not? can they put the path/to/script in a variable for you to access?)
2) Request that second.js be wrapped in a .done or equivalent loaded callback that pops a callback function that you can define.
// inside first.js
$.getScript("second.js").done(function(){
if (typeof 'secondLoaded'=='function')
secondLoaded();
});
// on-page or elsewhere, you define the callback
function secondLoaded() {
doSecond();
}
This is just a generic and easy "callback" example. There are a million ways to implement this principle, depending on what all is actually in these scripts and how much effort people are willing to make to restructure things.
3) Request that second.js script include be changed to be included via document.write
document.write(unescape("%3Cscript src='second.js' type='text/javascript'%3E%3C/script%3E"));
This will force js to resolve document.write before js can move on, so second.js should be loaded by the time you want to use doSecond(). But this is considered bad practice because until document.write is resolved, nothing else can happen. So if second.js is taking forever to load or eventually times out.. that makes for bad UX. So you should avoid this option unless you have no other choice because of "red tape" reasons.
4) use setTimeout to try and wait for it to load.
function secondLoaded() {
if (!secondLoaded.attempts) secondLoaded.attempts = 0;
if (secondLoaded.attempts < 5) {
if (typeof 'doSecond'=='function') {
doSecond();
} else {
secondLoaded.attempts++;
window.setTimeout('secondLoaded()',100);
}
}
}
secondLoaded();
I list this worse than #3 but really it's kind of a tossup.. In this situation you basically either have to pick between deciding a cutoff time to just not execute doSecond() (in this example, I try 5 times at 100ms intervals), or code it to just keep checking forever and ever (remove the .attempts logic or else swap it up w/ setInterval and removeInterval logic).
You could modify how $.getScript works.
$.fn.getScript = (function() {
var originalLoad = $.fn.getScript;
return function() {
originalLoad.apply($, arguments);
$(document).trigger('load_script')
};
})();
This will fire an event every time a script is loaded.
So you can wait for these events to fire and check if your method exists.
$(document).one('second_loaded', function() {
doSecond();
}).on('load_script', function() {
doSecond && document.trigger('second_loaded');
});
Note that one rather than on. It makes the event fire once.
Have you considered using jQuery.when:
$.when($.getScript("http://myexternaljs.com/first.js"),$.getScript("http://firstexternal.com/second.js"))
.done(function(){
doSecond();
}
If I were you, I'd facade $.getScript and perform some combination of the above tricks. After reading through the comments it seems that there is a possibility of loading the same script twice.
If you use requirejs this problem is solved for you because it only loads each script once. The answer here is to hang on to the requests made.
Loader:
var requiredScripts = {};
function importScript(url) {
if (!requiredScripts[url]) {
requiredScripts[url] = $.getScript(url);
}
return requiredScripts[url];
}
Usage:
// should cause 2 requests
$.when(
importScript('first.js'),
importScript('second.js')
).done(function() {
// should cause no requests
$.when(importScript('first.js')).done(function() {});
$.when(importScript('second.js')).done(function() {});
});
Real world example here using MomentJS and UnderscoreJS: http://jsfiddle.net/n3Mt5/
Of course requirejs would handle this for you and with better syntax.
Requirejs
define(function(require) {
var first = require('first'),
second = require('second');
});
$(window).one("second", function(e, t) {
if ( $(this).get(0).hasOwnProperty(e.type) && (typeof second === "function") ) {
second(); console.log(e.type, e.timeStamp - t);
$(this).off("second")
};
return !second
});
$.getScript("first.js")
.done(function( data, textStatus, jqxhr ) {
if ( textStatus === "success" ) {
first();
$.getScript("second.js")
.done(function( script, textStatus, jqxhr, callbacks ) {
var callbacks = $.Callbacks("once");
callbacks.add($(window).trigger("second", [ $.now() ]));
return ( textStatus === "success" && !!second
? callbacks.fire()
: $(":root").animate({top:"0"}, 1000, function() { callbacks.fire() })
)
});
};
})
// `first.js` : `function first() { console.log("first complete") }`
// `second.js` : `function second() { console.log("second complete") }`

Handling prerequsites load failure in RequireJS require function

I'm using RequireJS for AMD. Using this code I execute my function after ensuring the module1 is loaded:
require(['module1'], function (module1) {
if (module1) {
// My function code...
}
);
In some cases the module1 is not available (mostly because of access security). I want to handle what happens if module1 failed to load. Using some code like:
require(['module1'], function (module1) {
if (module1) {
// My function code...
}
)
.fail(function(message)
{
console.log('error while loading module: ' + message);
}
or maybe the require function accepts another parameter for module load failures?
So the question is, how can I handle if the required module failed to load?
See RequireJS API document: http://requirejs.org/docs/api.html#errors.
require(['jquery'], function ($) {
//Do something with $ here
}, function (err) {
//The errback, error callback
//The error has a list of modules that failed
var failedId = err.requireModules && err.requireModules[0];
if (failedId === 'jquery') {
//undef is function only on the global requirejs object.
//Use it to clear internal knowledge of jQuery. Any modules
//that were dependent on jQuery and in the middle of loading
//will not be loaded yet, they will wait until a valid jQuery
//does load.
requirejs.undef(failedId);
//Set the path to jQuery to local path
requirejs.config({
paths: {
jquery: 'local/jquery'
}
});
//Try again. Note that the above require callback
//with the "Do something with $ here" comment will
//be called if this new attempt to load jQuery succeeds.
require(['jquery'], function () {});
} else {
//Some other error. Maybe show message to the user.
}
});

Categories