I have plenty of JavaScript files, and take three as example: App.js, ChildApp.js(more than one),and AjaxManager.js, while AjaxManger.js is what I add.
ChildApp extends App, and App dependent on AjaxManager.
In App's constructors:
function App() {
this.ajaxManager=new AjaxManager();//Need AjaxManager.js to import, especially when new ChildApp is called.
....
}
ChildApp's constructor:
function ChildApp's() {
App.call(this); //
}
ChildApp.prototype = new App();
...
In the legacy code, there are tens of places where include App.js and I don't want to copy and paste the same code that includes AjaxManager.js before there.
But if I don't include AjaxManager well, I will have error when
var app=new ChildApp();//ReferenceError: AjaxManager is not defined
So I use below code IN THE BEGINNING of App.js:
if(typeof AjaxManager =='undefined'){
console.log('AjaxManager is not loaded , getting the script');
$.ajax({
url: .....//Url to load
dataType: "script",
async:false// How can I use async =true
});
}
But I have to use async:false for the request,which shows me a warning in the console that :Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience
Or I will have the error:
ReferenceError: AjaxManager is not defined
How can I use asynchronous request to load the script and sure things are working but will not break my existing functionality.
Update:
I tried $.getScript(url) in the beigining of App.js or in the begining of function App() before I ask this question but the same error, I guess something relative to the constructor App should be place in the callback but I don't know how.
I hope that I can modify app.js or ajaxmanager.js only since there are too many places where call new App() or new ChildApp()
Don't use async: false. It's extremely bad practice as it will block the UI thread until the request has completed.
You can instead use $.getScript to shorten the code, and use the callback handler to execute any code reliant on the script being loaded. Try this:
if (typeof AjaxManager =='undefined') {
$.getScript('ajaxManager.js', processPage);
}
else {
// AjaxManager already loaded
processPage();
}
function processPage() {
// put your logic that relies on AjaxManager here
}
If you want to load three scripts in a specific order with jQuery, you can use $.getScript() and chain each one into the completed promise of the previous one.
$.getScript('AjaxManager.js').then(function() {
return $.getScript('App.js');
}).then(function() {
return $.getScript('ChildApp.js');
});
Or, if you wanted to make this into a function that you could pass an array of scripts to:
function loadScripts(array) {
return array.reduce(function(p, scriptFile) {
return p.then(function() {
return $.getScript(scriptFile);
});
}, $.Deferred().resolve().promise());
}
loadScripts('AjaxManager.js', 'App.js', 'ChildApp.js');
// The order of the scripts in the array is the order they will be loaded.
If you want to test to see if they have previously been loaded, then you could also pass in a test symbol for each script and incorporate that into the loading logic.
function loadScripts(array) {
return array.reduce(function(p, obj) {
return p.then(function() {
if (typeof window[obj.testSymbol] === "undefined") {
return $.getScript(scriptFile);
} else {
return $.Deferred().resolve().promise();
}
});
}, $.Deferred().resolve().promise());
}
loadScripts(
{file: 'AjaxManager.js', testSymbol: 'AjaxManager'},
{file: 'App.js', testSymbol: 'App'},
{file: 'ChildApp.js', testSymbol: 'ChildApp'}
);
This would allow you to try to load any given script as many times as you wanted and it would only actually load once.
FYI, $.getScript() is an asynchronous operation. It calls the callback you pass it when it is done loading or resolves the promise it returns. If you want some operation to only proceed after $.getScript() has finished loading, then you MUST put that code in a callback.
So, if you wanted to load the AjaxManager script inside of the App() constructor, you can't do that before the constructor returns. It is not recommended in Javascript that you initiate asynchronous operations in a constructor because there is no good way to do error handling and you may have a partially initialized object until the asynchronous operation has completed.
See the 2nd half of this answer for some comments about async operations in constructors.
Related
I'm trying to use the NodeJS module "pcsc-lite" to communicate with a card reader. If you want to take a look at the module : https://github.com/santigimeno/node-pcsclite.
I'm looking for a way to send a sequence of data to my reader using my own method. Because, the module is event-based. So I have to declare two listeners (one in the other) to be able to call the send method.
For example :
module.on("reader", function(reader){
//...
reader.on("status", function(status){
//...
reader.connect({ share_mode : this.SCARD_SHARE_SHARED },function(err, protocol) {
//This is the method I want to be able to call "when I need it"
reader.transmit(...);
});
});
});
I would like to call the transmit method like this for example :
function send(...){
reader.transmit(...);
}
I think there is a way to do it, but I seem to be a little bit hooked to my C/Java programming habits.
Thanks in advance.
If your reader will be a singleton, you can declare it outside the callback, and then assign the variable when you're ready. Without knowing more, here's a simple example:
let reader; // we prepare a variable that's outside of scope of it all.
// your `send` function
function send(params) {
let stuff = doStuffWithParams(params);
reader.transmit(stuff, callback);
}
// we take out init stuff too
function initialize() {
// we know reader variable is already initialized.
reader.on('status', function() {
reader.connect({
share_mode : this.SCARD_SHARE_SHARED
},function(err, protocol) {
// send.
send();
// or even better, emit some event or call some callback from here, to let somebody outside this module know you're ready, then they can call your `send` method.
});
});
}
// now your module init section
let pcsc = require('pcsclite')();
pcsc.on('reader', function(r) {
// assign it to our global reader
reader = r;
initialize();
});
Note: don't call your variables module, it's refering to the file being currently executed and you can get unexpected behavior.
I have the following code I've designed to load and run script at runtime. You'll note that I save it to localStorage if it isn't already there. Now it runs fine if it's stored there already, but when it's just got the text from the file it throws ReferenceError: loginLaunch is not defined, though the text seems to have been loaded (hence the console.log lines that check the length). For your convenience I've included a line, localStorage.clear();, to make it alternate between the error message that's the problem and ReferenceError: loginLaunch is not defined, which given the code below is the desired result.
I don't understand why it should work one way and not the other. If it's a timing issue I don't see how the use of the promise, loginCode, lets it through unless possibly appendChild() is asynchronous, but I'm under the impression that it isn't (mainly because it has no callback, and I tried to find out, but could not) and even then why would code before the appendChild() have an impact?
Have I messed up one of the promises? I include the contents of the file login.js at the end. I searched SO for anything relevant but without any luck except for just one post that states that appendChild is synchronous.
Please help.
var loginCode = runCode("login_1001","./js/login.js");
loginCode.done(loginLaunch());
//FUNCTIONS START HERE
function getCode(local, source) { //This creates the promise to get the code (not to run it)
console.log("start of loadCode");
dfd = $.Deferred(); //This is the one to return.
script = localStorage.getItem(local); //Try to load from local storage.
// console.log("script after local attempt: "+script);
if (script) { //If found...
console.log("found Local code");
dfd.resolve(script);
localStorage.clear(); //Added for debugging
} else { //load from file.
ajax = $.ajax({
url : source,
cache : false,
dataType : "text", //load as text initially so that we can store it locally.
});
ajax.done(function(fromFile){
localStorage.setItem(local, fromFile); //store it locally.
//console.log("script after ajax attempt: "+script);
dfd.resolve(fromFile);
});
ajax.fail(function(){
dfd.reject("Error retrieving code. You may be disconnected");
});
}
return dfd.promise();
}
function runCode(local, source) {
dfd = $.Deferred(); //This is the one to return.
code = getCode(local, source); //local promise
code.done(function(retrievedCode){
console.log(retrievedCode.length);
var head = document.getElementsByTagName('head')[0]; //first head section
var el = document.createElement("script"); //named the same as the local storage
//script.type= 'text/javascript'; Redundant — it's the default
// el.id = local; //Probably redundant, but if we want to manipulate it later...
el.text = retrievedCode;
head.appendChild(el); //This shouldn't run anything, just make global functions that will be called later.
console.log(el.text.length);
dfd.resolve(); //If we need to return the node to manipulate it later we'd make the variable above and 'return' it here
});
return dfd.promise();
}
Here's the contents of the login.js file.
function loginLaunch(){
dfd = $.Deferred(); //This is the one to return.
loadElement("login.1001", "#content", "login.html");
//After the element has been loaded we have a disconnect — i.e. there's no promise waiting, so we have to wait for the user.
}
$("#content").delegate('#loginButton','click',function(){
console.log("Login click");
//php to pick up the entered details and pass them to common php that also uses the
checkCredentials = $.ajax({
type : "POST",
url : "./php/credentials.php",
data : {
queryString : queryString
},
datatype : "text", // 1 or 0
});
checkCredentials.done(credentialsChecked(success));
// MOVE THIS STUFF
readyPublicList();
$.when(publicListCode,loggedIn).then(runDefaultPublicList()); //Assumes successful login so it loads the code for the list window in parallel.
//Note that it's probable that my approach to the login window may change, because it needs to be available on the fly too.
// $("#content").html("<p>test</p>"); //Successfully tested, well it was once.
});
function loginHide(){
$("#loginHtml").hide;
}
I'm not sure why this works:
var loginCode = runCode("login_1001","./js/login.js");
loginCode.done(function(){loginLaunch();});
and this doesn't:
var loginCode = runCode("login_1001","./js/login.js");
loginCode.done(loginLaunch);
My one thought is that maybe if you pass literal named functions to .done then they are validated when loginCode is created, while anonymous functions aren't validated until they are about to be run.
I should note that the error was appearing before the console.log output.
Maybe someone with a better grasp of the technicalities can clarify. For now I'm just happy to stop tearing my hair out, but I like to know how things work...
You need to change at least three things. First change this:
loginCode.done(loginLaunch());
to this:
loginCode.done(function() {loginLaunch()});
You need to be passing a function reference to the .done() handler so it can be called later. The way you had it, you were calling it immediately BEFORE loginCode() was done with its work, thus it was getting called too early.
In addition, loginLaunch doesn't exist yet so you can't pass a reference directly to it. Instead, you can pass a reference to a wrapper function that then calls loginLaunch() only after it finally exists.
And second, you need to declare your local variables with var so they aren't implicit globals and stomp on each other. For example, you have multiple functions who call each other trying to use the same global dfd. That is a recipe for disaster. Put var in front of it to make it a local variable so it's unique to that scope.
And third, el.text doesn't look like the right property to me for your script. Perhaps you meant to use .textContent or since you have jQuery, you can do:
$(el).text(retrievedCode);
In a couple style-related issue, ALL local variables should be declared with var before them so they are not implicit globals. This will bite you hard by causing mysterious, hard to track down bugs, even more so with async code.
And, you can generally use the promise returned by jQuery from ajax functions rather than creating your own.
To incorporate those improvements:
runCode("login_1001","./js/login.js").done(loginLaunch);
function getCode(local, source) { //This creates the promise to get the code (not to run it)
var script = localStorage.getItem(local); //Try to load from local storage.
if (script) { //If found...
localStorage.clear(); //Added for debugging
// return a resolved promise (since there's no async here)
return $.Deferred().resolve(script);
} else { //load from file.
// return the ajax promise
return $.ajax({
url : source,
cache : false,
dataType : "text", //load as text initially so that we can store it locally.
}).then(function(fromFile){
localStorage.setItem(local, fromFile); //store it locally.
return fromFile;
});
}
}
function runCode(local, source) {
return getCode(local, source).then(function(retrievedCode){
console.log(retrievedCode.length);
var head = document.getElementsByTagName('head')[0]; //first head section
var el = document.createElement("script"); //named the same as the local storage
$(el).text(retrievedCode);
head.appendChild(el); //This shouldn't run anything, just make global functions that will be called later.
console.log(el.text.length);
});
}
FYI, if you just want to insert a script file, you don't have to manually retrieve the script with ajax yourself. You can use the src property on a script tag and let the browser do the loading for you. You can see a couple ways to do that here and here.
I'm trying to initialize a variable in javascript (specifically, I want to use a remote template with the jQuery template plugin) and then have multiple asynchronous callbacks wait for it to be initialized before proceeding. What I really want is to be able to link to the remote template via a <script type="text/x-jquery-tmpl" src="/my/remote_template"> tag, but barring that I could get away with the javascript equivalent of a pthread_once.
Ideally, the api would look something like:
$.once(function_to_be_called_once, function_to_be_called_after_first)
And used like:
var remote_template = "";
function init_remote_template() {
remote_template = $.get( {
url: "/my/remote/template",
async: false
});
}
$.once(init_remote_template, function () {
// Add initial things using remote template.
});
And then later, elsewhere:
$.get({
url: "/something/that/requires/an/asynchronous/callback",
success: function () {
$.once(init_remote_template, function () {
// Do something using remote template.
}
}
});
Does such a thing exist?
Looks like jQuery's promises can help you out here:
var templatePromise = $.get({
url: "/my/remote/template"
});
templatePromise.done(function(template) {
// Add initial things using remote template.
});
and elsewhere you can do:
$.get({
url: "/something/that/requires/an/asynchronous/callback",
success: function () {
templatePromise.done(function(template) {
// Do more things using remote template.
});
}
});
Usually $.get (and $.ajax, etc) are used with success: and error: callbacks in the initial invocation, but they also return a promise object that acts just like a $.Deferred, documented here: http://api.jquery.com/category/deferred-object/ which allows you to do what you're asking. For error handling, you can use templatePromise.fail(...) or simply add error: ... to the initial $.get.
In general it's best to avoid synchronous AJAX calls because most browsers' interfaces will block while the HTTP request is being processed.
If I understand correctly, jQuery will do what you want of it by way of Deferreds/promises.
You can even generalise the remote template fetcher by
using a js plain object in which to cache any number of templates
renaming the function and passing a url to it, get_remote_template(url)
js :
var template_cache = {};//generalised template cache
//generalised template fetcher
function get_remote_template(url) {
var dfrd = $.Deferred();
if(!template_cache[url]) {
$.get(url).done(function(tpl) {
template_cache[url] = tpl; //we use the url as a key.
dfrd.resolve(tpl);
});
}
else {
dfrd.resolve(template_cache[url]);
}
return dfrd.promise();
}
Then :
var url1 = "/my/remote/template"; //the url of a particular template
get_remote_template(url1).done(function(tpl) {
// Add initial things using tpl.
});
And, earlier or later :
$.get({
url: "/something/that/requires/an/asynchronous/callback",
success: function(data) {
init_remote_template(url1).done(function (tpl) {
// Do something using tpl (and data).
});
}
});
Note how get_remote_template() returns a promise. If the requested template is already cached, the promise is returned ready-resolved. If the template is not yet in cache (ie. it needs to be downloaded from the server), then the promise will be resolved some short time later. Either way, the fact that a promise is returned allows a .done() command to be chained, and for the appropriate template to be accessed and used.
EDIT
Taking #SophieAlpert's points on board, this version caches the promise associated with a tpl rather than the tpl itself.
var template_cache = {};//generalised cache of promises associated with templates.
//generalised template fetcher
function get_remote_template(url) {
if(!template_cache[url]) {
template_cache[url] = $.get(url);
}
return template_cache[url];//this is a promise
}
This version of get_remote_template() would be used in the same way as above.
I have a script with the following structure:
Test = {
CONSTANTS : {},
VARIABLES : {},
MARKUP : {},
FUNCTIONS : {
init : function () {
// Access variable from different namespace
var all_constants = DifferentNamespace.CONSTANTS; // WORKS
var tester = DifferentNamespace.CONSTANTS.chunk_of_markup; // SAYS UNDEFINED
}
},
init : function () {
// Call real init() function
$(document).ready(function () {
Test.FUNCTIONS.init();
});
}
};
$(document).ready(function () {
Test.init();
});
If I remove either of the $(document).ready(..) function calls, when I try to access a constant from a different namespace it is undefined; with both is works well.
As you can see I'm using two init() functions, one it just to neaten up the call to init because I have wrapped functions inside an additional object.
If I remove the function that is on the same level as CONSTANTS, VARIABLES etc and try to call the init() within Test.FUNCTIONS it still does not work.
Edit:
If i console.log(all_constants) I get the full object (with .chunk_of_markup) but if I console.log(tester) is get undefined. If i wrap tester i get []
I should also note that the other namespace gets the markup from a seperate file.
Any ideas why?
Having two document ready doesn't make a difference here. You could have one document.ready and/or call Test.FUNCTIONS.init directly and all should work, and the fact that they are in different namespaces doesn't matter as well.
As for why you're getting undefined, I think it is probably because your chunk_of_markup variable is actually undefined at that point. My guess is that you're getting the value for it through AJAX and so the call is done asynchronously which means the DOM will be ready before it actually returns a value. When you use the Debugger then the value is evaluated at the point of time where you run the command so by then, the async call already returns successfully (it's a race condition, if you're fast enough and your AJAX is slow then you can still get undefined, and it's also why 2 ready functions happen to make it slow enough for the AJAX call to return but it's still unreliable).
In all cases, if my theory is correct, then you need to hook to the callback of the AJAX request rather that DOM ready event, this is the only place where you can guarantee that your variable is defined.
Why not call the function init() in the document Handler itself.. I don't think that will lead to the same problems.. You can remove the Test.init() completely as it does not seem to do anything in here
Test = {
CONSTANTS : {},
VARIABLES : {},
MARKUP : {},
FUNCTIONS : {
init : function () {
// Access variable from different namespace
var all_constants = DifferentNamespace.CONSTANTS; // WORKS
var tester = DifferentNamespace.CONSTANTS.chunk_of_markup; // SAYS UNDEFINED
}
}
};
$(document).ready(function () {
Test.FUNCTIONS.init();
});
I am working with a user control that has set of javascript functions that are called when an action is performed. This user control is used in a lot of places in the application.
When one of the inbuilt JS function completes execution, I need to fire a custom JS function on my page.
Is there a way for me to attach a function to be fired when another function completes execution? I don't want to update the inbuilt JS function to call this page JS function.
Hope this makes sense.
There are a couple design patterns you could use for this depending upon the specific code (which you have not shared) and what you can and cannot change:
Option 1: Add a callback to some existing code:
function mainFunction(callbackWhenDone) {
// do other stuff here
callbackWhenDone();
}
So, you can call this with:
mainFunction(myFunction);
Option 2: Wrap previous function:
obj.oldMethod = obj.mainFunction;
obj.mainFunction = function() {
this.oldMethod.apply(this, arguments);
// call your stuff here after executing the old method
myFunction();
}
So, now anytime someone does:
obj.mainFunction();
it will call the original method and then call your function.
You're basically trying to do callbacks. Since you're not mentioning what functions you're talking about (as in code), the best thing to do would be basically to wrap the function, -quick and dirty- and make it work with callbacks.
That way you can pass it a Lambda (Anonymous Function) and execute anything you want when it's done.
Updated to demonstrate how to add Callbacks:
function my_function($a, $callback) {
alert($a);
$callback();
}
my_function('argument', function() {
alert('Completed');
});
The ugliest and best solution is to monkey-patch the built-in function. Assume the built-in function is called "thirdParty":
// first, store a ref to the original
var copyOfThirdParty = thirdParty;
// then, redefine it
var thirdParty = function() {
// call the original first (passing any necessary args on through)
copyOfThirdParty.apply(this, arguments);
// then do whatever you want when it's done;
// custom code goes here
customFunction();
};
We've essentially created a modified version of the built-in function without ever touching the original version.
Since Javascript is highly dynamic you can modify the original function without modifying its source code:
function connect_after(before, after){
return function(){
before.apply(this, arguments);
after();
};
}
var original_function = function(){ console.log(1); }
original_function = connect_after(original_function, function(){ console.log(2); })