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.
Related
I'm trying to read data from a json file into JavaScript so it can be used by other functions that are called when a user interacts with the page. I've tried, using jQuery and JS:
var products = null;
$.getJSON("products.json", function(data) {
products = data;
console.log(data);
});
console.log(products);
Which produces, as expected:
null
Array [ {…}, {…} ]
I understand this is because of the asynchronous nature of the jQuery code execution. I've read into it a bit, but I'm just struggling to wrap my head around how to re-structure my code (coming from pretty much exclusively Python).
This is how I'm seeing it:
var products = null;
$.getJSON("products.json", function(data) {
products = data;
console.log(data);
});
function do_stuff(){
// Access the attributes of data and do stuff
}
function do_more_stuff(){
// Do some more stuff with the data
}
I would probably have do_stuff() execute when the page body loads, and let's say do_more_stuff executes when the user selects something in a dropdown menu. Both need to access the data in products.json. I know there are many examples out there, but I'm just not getting it. How can I re-structure the code to actually work?
I believe you are looking for something like this:
function get_json(cb) {
$.getJSON("products.json", function(data) {
cb(data);
});
}
function cb(data) {
// do stuff here if you want
console.log(data)
}
get_json(cb)
Create a callback function cb (or call it do_stuff if you'd like). Pass a reference to that function to your async function (get_json). When the async operation is complete, call your callback function with the data you received.
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.
I'm developing a webpage that retrieves some database info via a (jquery) ajax call and then manipulates the data and displays it on the page via various javascript functions. I use a module pattern ('userapp' in the code example below) to avoid globals and to use the data by the various functions.
The simplified code below works, but only if I include all the functions (such test() in the code below) within the ajax call, and I think that's going to result in ugly complex code (also in line with some best javascript practises I read on the web). When I tried to call the example function test() outside /after the ajax call (embedded in init()), test() does not work as I think the ajax call has not completed setting the variables ('products[]' in example) yet.
My question: is it possible to call other functions outside the ajax call, and if so, how? I looked at callback examples but I'm not sure whether/how that would solve my problem...
simplified code:
userapp = function(){
//userapp properties
var today = new Date();
var products = [];
var copyItems = function(source_items, target_items){
//do something
};//var copyItems
var init = function(){
$.ajax({
url: "user_admin_get.php",
data: { command: "getuserbaseinfo", val1: "", val2: "" },
success: function (msg) {
copyItems(msg.eproducts,products); //set values for 'products' used by test()
test(); //calling test() here works as the the values has been set()
},//success: function(msg)
error: function(msg) {
console.log('error result from php file:');
},//error:
dataType: "json"
});//$.ajax({
};//var init = function(){
var test = function(){
//do something
};//test()
return{init:init, test:test} //return (only) public functions and vars
}(); //userapp()
//main function document ready
$(document).ready(function(){
userapp.init();
//userapp.test(); //test() is not working here as init() has not set the requirement parameters tey
}); //$(document).ready
You want to pass a callback to init and call test inside this callback.
var init = function(callback){
$.ajax({
....
success: function (msg) {
....
callback();
}
...
};
...
userapp.init(function() {
// user app is ready!
userapp.test();
});
I have common module on my app that retrieves data from remote server and have get methods to export the retrieved data to other modules.
Lets assume that this module's name is MyData:
define([], function(){
return function(opts) {
var data = null;
// go get data with ajax
var getData = function(){
$.ajax("getData").done(function(response){
data = response;
});
};
getData();
// return public api
if (arguments.length !== 0) {
var method = arguments[0];
switch (method) {
case "get": return data;
}
};
};
});
The problem is when I define other modules that use MyData, everytime it would create a new instance of MyData, causing it to retrieve the data from the server each time.
I could use web storage (or other global variable) to store the returned data, but it seems like a bad solution.
Is there any way to force RequireJS to create only once instance?
P.S:
In my project I user KnockoutJS, and the data variable is an ko.observable, so maybe knockout has a solution for my situation?
RequireJS already loads each module once. You just need to restructure your code a bit:
// myModule.js
define(function() {
// The dataFetch Promise will be initialized when this module
// is loaded for the first time.
var dataFetch = $.ajax("getData");
return {
getData: function () {
// Return the Promise interface and let callers use the
// .done, .fail etc. methods on it
return dataFetch;
}
};
});
Usage:
define([ "myModule" ], function (myModule) {
myModule.getData().done(function (data) {
// use the data
});
});
By using the Promise interface returned by $.ajax, you can ensure asynch access to data that's fetched only once.
BTW, perhaps the pattern that you're trying to implement is a Model.
So I have a common scenario where everything depends on AJAX responses, followed by possibly more AJAX responses.
What ends up happening is lots and lots of presentation (page-specific) code gets thrown inside the success() callback:
$.ajax({
...
success: function (response) {
// too much $('#something').html() crap goes in here!
}
});
What is the best practice for removing this "data access" code completely from the presentation code but maintaining the loading sequence?
I've used deferred calls like $.when().then() but that still seems sloppy to me. Is there anything better?
To make an answer even simpler, let's say I want to retrieve information about a Person object and segregate all that logic into its own area. Example:
note: this code will not work - I am aware of that
Person.js:
var Person = {
getByID: function(id) {
// ajax call to return a person object (format is arbitrary)
// { id: 12345, name: 'Joe Smith', email: 'joe#smith.com }
}
};
SomePage.html
var myID = 12345; // get ID from wherever
var person = Person.getByID(myID);
$('#person .name').html(person.name);
$('#person .email').html(person.email);
EDIT: My solution
Although many of the answers were helpful, I chose to pass callback functions that separate all the various pieces of logic from each other. Example code:
JS:
Person = {
get: function(params, callback) {
$.ajax({
url: '/person/get',
data: params,
success: callback
});
}
};
Pages = {
Person: {
render: function(person) {
// in reality I'm using templates, this is for simplicity
$('#person .name').html(person.name);
$('#person .email').html(person.email);
}
}
};
SomePage.html
$('#someElement').someEvent(function() {
var params = {
id: 12345
};
Person.get(params, Pages.Person.render);
}
I should also add I found this slide deck extremely informative:
http://speakerdeck.com/u/addyosmani/p/large-scale-javascript-application-architecture
You'll have to have something in the success callback, but you don't need presentation details there. The success call could simply call:
Person.render(attributes, element)
And the details would be in the render function.
As SLaks says, what you want to do is impossible in the asynchronous context of AJAX.
However, nothing speaks against keeping the majority of the code in separate objects that do all the grunt work. You would then call those objects and their methods from your success callbacks and pass them all the necessary data. That would ensure that your callbacks contain only the bare minimum of code.
It sounds to me that you may wish to introduce the Model-View-Controller design pattern into your application. In the simplest form the Model would be responsible for fetching the required data which is then passed through to the View to be rendered; the Controller's job is to process incoming requests, invoke the Model and shuffle the data into the View in the expected format.
As others have mentioned there are plenty of lightweight MVC frameworks out there for JavaScript; but you should be able get the basic concept up and running just by using jQuery and a templating engine (such as Mustache.js) so you can get some separation between Model (fetching the data and building a common object graph) and View (rendering a template and adding the rendered HTML to the DOM).
As for you proposed Person example, you could make use of jQuery's Deferred to allow Person.getById to return a Promise, eg:
var Person = {
getByID: function(id) {
var result;
// Return a Promise which we will resolve once the AJAX call completes.
return $.Deferred(function (dfd) {
$.ajax(...)
.done(function (response) {
// Parse the AJAX response into your app's model data.
result = {
id: response.id,
name: response.firstName,
email: response.email
};
// Resolve the Promise and supply the Person object.
dfd.resolve(person);
});
}).promise();
}
};
Your Controller can then invoke the Model and then supply the result to the View (this is where you could make use of templates).
Person.getByID(myID)
.done(function(person) {
$('#person .name').html(person.name);
$('#person .email').html(person.email);
});
You can slightly change your example to make it work asynchronously - still cleaner code than shoving everything in to the success callback function:
var myID = 12345; // get ID from wherever
Person.getByID(myID, function(person) {
$('#person .name').html(person.name);
$('#person .email').html(person.email);
});
I now know the correct answer is:
There are over 9000 libraries created specifically for this, a common problem known as AMD.
-TerryR
http://speakerdeck.com/u/addyosmani/p/large-scale-javascript-application-architecture