I've inherited JavaScript code where the success callback of an Ajax handler initiates another Ajax call where the success callback may or may not initiate another Ajax call. This leads to deeply nested anonymous functions. Maybe there is a clever programming pattern that avoids the deep-nesting and is more DRY. Also, there is the problem of inner variables myVar1 and myVar2 that are used throughout the functions.
jQuery.extend(Application.Model.prototype, {
process: function() {
var myVar1;
// processing using myVar1;
jQuery.ajax({
url:myurl1,
dataType:'json',
success:function(data) {
var myVar2;
// process data using myVar1, set state of myVar2,
// then send it back
jQuery.ajax({
url:myurl2,
dataType:'json',
success:function(data) {
// do stuff with myVar1 and myVar2
if(!data.ok) {
jQuery.ajax({
url:myurl2,
dataType:'json',
success:mycallback
});
}
else {
mycallback(data);
}
}
});
}
});
}
});
There's no need for all the callbacks to be anonymous and defined inline, you can declare them elsewhere and just use the function name when specifying the callback.
Thanks to the chaining hint and this comment, I have come to the following solution. I have tested it and it works. There are probably some scope issues and you could refactor a general ChainAjax class out of it. But for the time being, this is ok.
jQuery.extend(MyApplication.Model.prototype, {
process: function() {
// private class for executing the Ajax calls
var myAjaxCalls = function(options) {
this.options = options;
this.myVar1 = null;
this.myVar2 =null;
}
jQuery.extend(myAjaxCalls.prototype, {
process1:function(data) {
// processsing using this.myVar1
this.myVar1 = 5;
return true;
},
process2:function(data) {
this.myVar2 = 6;
if(data.ok) {
mycallback(data);
}
else {
return true;
}
},
process3:function(data) {
// Process this.myVar1 and this.myVar
mycallback(data);
return false;
},
chainAjax:function() {
if(this.options.length > 0) {
var opt = this.options.shift();
var that = this;
jQuery.ajax({
url:opt.url,
success:function(data) {
if(that[opt.callback](data)) {
that.chainAjax();
}
}
});
}
}
});
// End private class
var calls = new myAjaxCalls([
{url:'http://localhost/', callback:'process1'},
{url:'http://localhost/', callback:'process2'},
{url:'http://localhost/', callback:'process3'}
]);
calls.chainAjax();
}
});
Update: I found this nice presentation that also deals with useful programming patterns and best practices.
Update 2012: In the meantime there are several libraries for simulating a synchronous flow with asynchronous functions: q, stratified.js and streamline.js
I would suggest creating a little tool called "chain ajax". You give it what you want to happen in what order, and then fire. It will chain ajax on success until all the logic runs out. It will help you stop repeating yourself and just represent the logical model of what you want done vs grunt-coding.
You could do this with Frame.js like this:
jQuery.extend(Application.Model.prototype, {
process: function() {
var myVar1;
// processing using myVar1;
Frame(function(done){
jQuery.ajax({
url:myurl1,
dataType:'json',
success: done
});
});
Frame(function(done, data) {
var myVar2;
// process data using myVar1, set state of myVar2,
// then send it back
jQuery.ajax({
url:myurl2,
dataType:'json',
success: done
});
});
Frame(function(done, data) {
// do stuff with myVar1 and myVar2
if(!data.ok) {
jQuery.ajax({
url:myurl2,
dataType:'json',
success:done
});
}
else {
done(data);
}
});
Frame(function(done, data){
mycallback(data);
});
Frame.start();
}
});
Related
I have to create lot of functions that contain almost same pattern and coding.
Creating multiple function becomes more useful to use different purpose and pages for my project. For example:
function cls(){
$.ajax({
url:"crud/fetch.php",
method:"POST",
data:{cat:'cls'},
success:function(data){
$('#cls').html(data);
}
});
}
function stdt(){
$.ajax({
url:"crud/fetch.php",
method:"POST",
data:{cat:'stdt'},
success:function(data){
$('#stdt').html(data);
}
});
}
function sec(){
......
//same pattern
}
function pdl(){
......
//same pattern
}
I tried to contain these function in one function to reduce code that seems clean, easy for debugging and re-editing.
So I tried storing all desired function name in one array and create function using each index.
But I am getting Uncaught TypeError: cls is not a function. I have tried without using window[cat]. I think it is foolish way, but tried, hoping it can works. Please suggest how can I assign or create function using each array index value.
var menu = ["cls", "stdt", "sec", "pdl", "sub", "xsub", "cls_sub", "cls_xsub", "xam", "mrksch", "grdsch", "sclnfo"];
$.each(menu, function(i,cat){
var ftch = window[cat];
function ftch(){
$.ajax({
url:"crud/fetch.php",
method:"POST",
data:{menu:cat},
success:function(data){
$('#"' + cat+ '"';).html(data);
}
});
}
})
You can use anonymous functions. Also, quick side note, you have a syntax error with $('#"' + cat+ '"';):
var menu = ["cls", "stdt", "sec", "pdl", "sub", "xsub", "cls_sub", "cls_xsub", "xam", "mrksch", "grdsch", "sclnfo"];
$.each(menu, function(i,cat){
window[cat] = function () {
$.ajax({
url:"crud/fetch.php",
method:"POST",
data:{menu:cat},
success:function(data){
$('#' + cat).html(data);
}
});
}
});
Although, I would highly recommend that you create a custom variable/class, to avoid too much pollution to the global scope:
window.fetch = {};
var menu = ["cls", "stdt", "sec", "pdl", "sub", "xsub", "cls_sub", "cls_xsub", "xam", "mrksch", "grdsch", "sclnfo"];
$.each(menu, function(i,cat){
window.fetch[cat] = function () {
$.ajax({
url:"crud/fetch.php",
method:"POST",
data:{menu:cat},
success:function(data){
$('#"' + cat+ '"';).html(data);
}
});
}
});
You can even make the above approach dynamic with a Proxy (This is just a demonstration, you should simply just create a function with a parameter):
var fetchMenu = new Proxy({}, {
get: function(obj, cat, val) {
return () => {
console.log('Lets load ' + cat);
};
}
});
var menu = ["cls", "stdt", "sec", "pdl", "sub", "xsub", "cls_sub", "cls_xsub", "xam", "mrksch", "grdsch", "sclnfo"];
fetchMenu.cls();
Although, this appears to be an X/Y issue. What's your reasoning for doing this? Why not just create a function that takes a parameter for what to fetch?
function fetchCat(cat) {
$.ajax({
url:"crud/fetch.php",
method:"POST",
data:{menu:cat},
success:function(data){
$('#' + cat).html(data);
}
});
}
Later on...you can do something as simple as:
$.each(menu, (i, cat) => fetchCat(cat));
You are trying to call a string in this code
var ftch = window[cat];
function ftch(){
I'm guessing you think this means create a function called cat on the window object, which you are then defining with ftch, however this simply gets the object stored at window[cat] (which will be undefined) and then tries to create another function called ftch.
To fix this, simply change your code to:
window[cat] = function(){
$.ajax({
url:"crud/fetch.php",
method:"POST",
data:{menu:cat},
success:function(data){
$('#"' + cat+ '"';).html(data);
}
});
}
Try calling the function using the window command
window(function_name,parameters);
I have a simple code that involves asynchronous tasks:
// The NewsFeed Class
function NewsFeed() {
this.loadFeed = function() {
$.ajax({
url: "http://www.example.com",
success: function() {
// doSomething here, and call onload.
}
});
}
// Need to implement onload here somehow
this.onload = ?;
this.loadFeed();
return this;
}
NewsFeed.constructor = NewsFeed;
// In main JS file
var newsFeed = new NewsFeed();
$(function() {
// do something
newsFeed.onload = function() { // do something when news feed is loaded };
}
My requirement is that, onload of NewsFeed needed to be executed in both case:
If the loadFeed's ajax is finished, run it immediately.
If the loadFeed's ajax is not done yet, run after it.
There's really no need to use new or constructor when you don't need new instances, all you really need is to run a simple ajax function that gets the result from cache if it hasn't changed.
function newsFeed() {
return $.ajax({
url : "http://www.example.com",
cache : true // let the browser handle caching for you
});
}
// In main JS file
$(function() {
newsFeed().then(function() {
// do something when news feed is loaded
});
});
The new pattern instead of callback is using Promises
see:
https://github.com/kriskowal/q
With jquery you can use:
https://api.jquery.com/category/deferred-object/
now the code:
function NewsFeed() {
function loadFeed() {
var deferred = $.Deferred();
$.ajax({
url: "http://www.example.com",
success: function(data) {
deferred.resolve(data);
},
error: function(data) {
deferred.reject(data);
}
});
return deferred.promise();
}
this.loadFeed = loadFeed;
return this;
}
NewsFeed.constructor = NewsFeed;
// In main JS file
var newsFeed = new NewsFeed();
newsFeed.loadFeed().done(function(data){
//data loaded successfully
})
.fail(function(data){
//ajax request failed
})
.always(function(){
//finally:
});
I need to execute 3 ajax requests. I know that they happen to be asynchronous by default (And making them synchronous messes up the VM, so I don't want to go that way.) The way I do it is by calling a function three times passing variables.
result = '';
parse(var1);
parse(var2);
parse(var3);
view();
function parse(variable) {
//ajax request here
$.ajax({
type: 'POST',
url: 'script.php',
data: {variable: variable},
success: function (data) {
//result stored in a global variable
result += data;
}
});
}
function view() {
//do something with result
}
But right now, the view() is triggered right away when the result isn't done cooking. How do I set them up to happen one after the other? I read about callbacks but they are very confusing since I don't have 3 distinct functions but just one taking different variables.
You could store your variables in an array and use a function to make your ajax call:
var variables = [var1, var2, var3];
function callParse() {
if(variables.length) {
var currentVar = variables.shift();
parse(currentVar);
}
else {
view();
}
}
function parse(variable){
//ajax request here
$.ajax({
type:"POST",
url:'script.php',
data:{variable:variable},
success:function(data)
{
result+=data;
callParse();
}
});
}
function view(){
//do something with result
}
Try chained promises - from: https://css-tricks.com/multiple-simultaneous-ajax-requests-one-callback-jquery/
$.when(
// Get the HTML
$.get("/feature/", function(html) {
globalStore.html = html;
}),
// Get the CSS
$.get("/assets/feature.css", function(css) {
globalStore.css = css;
}),
// Get the JS
$.getScript("/assets/feature.js")
).then(function() {
// All is ready now, so...
// Add CSS to page
$("<style />").html(globalStore.css).appendTo("head");
// Add HTML to page
$("body").append(globalStore.html);
});
You could try doing it this way:
parseAndView([var1, var2, var3]);
function parseAndView(vars, index) {
index = index || 0; //initialize index if not passed
//execute the AJAX call only if there are more variables to parse
if (index < vars.length)
//ajax request here
$.ajax({
type: "POST",
url: 'script.php',
data: {variable: vars[index]},
success: function (data) {
// result stored in a global variable
result += data;
// recursive call to parse another variable
parseAndView(vars, index++);
}
});
else
view();
}
function view() {
//do something with result
}
I know it sounds like something that's been asked before, but for all my hunting, I can't find anything matching what I'm looking for.
I'm working on a project that's rather heavily based on Ajax. I'm using jQuery, but even with its beautifully streamlined code, it's still messy when I've got it to the point that the code is exactly the same, except for one single command passed through the data field.
So I tried setting it up inside a handler function, like so:
function _call(task, opts, async) {
if(typeof async !== "boolean") { async = true; }
opts = $.extend({}, opts, options);
$.ajax({
url: "myphpfile.php",
dataType:"JSON",
type:"POST",
async:async,
data: { task: task, opts: opts }
}).done(function(data) { return data; });
}
For those who read through, you'll notice that there's a var,
options, that hasn't been defined in the example. It has actually
been assigned, it's just been omitted for clarity purposes.
I came to realize that this doesn't work, as even when it's set to async: false, the code still continues after the call of _call(...), therefore not getting the results in time. I've tried a few different variations, including passing an anonymous function to the handler and then using that as the .done() function, but it wouldn't interact with outside variables, defeating the purpose.
All I'm looking for is a system that will let me use it something like this:
var returnedData = _call("thisismytask");
var returnedDataWithOptions = _call("thisisanothertask", {'option': 'option1'});
I really hope this is possible. I'm sure it would be, as the main purpose of functions is to remove the need for unnecessarily repeated code.
Thanks. :)
You could do this:
function _call(task, opts) {
opts = $.extend({}, opts, options);
return $.ajax({
url: "myphpfile.php",
dataType:"JSON",
type:"POST",
data: { task: task, opts: opts }
});
}
It removes the need for passing a callback into _call, but functions just the same.
_call("thisisanothertask", {'option': 'option1'}).then(function(data) {
// function body here
});
you are probably best doing this
function _call(task, opts, callback) {
opts = $.extend({}, opts, options);
$.ajax({
url: "myphpfile.php",
dataType:"JSON",
type:"POST",
data: { task: task, opts: opts }
}).done(function(data) { callback(data); });
}
use like this
_call("thisisanothertask", {'option': 'option1'}, function(data) {
//do things with data
});
You cant really do this, as _call cant return straight away as it may take a while to get a response and even if you could it could lock your browser which is not good.
var returnedData = _call("thisismytask");
var returnedDataWithOptions = _call("thisisanothertask", {'option': 'option1'});
you need to use it like this:
_call("thisismytask", null, function(returnedData) {
//use returnData
});
_call("thisisanothertask", {'option': 'option1'}, function(returnedDataWithOptions) {
//use returnedDataWithOptions
});
If you needed the result of thisismytask before thisisanothertask you would have to do:
_call("thisismytask", null, function(returnedData) {
_call("thisisanothertask", {'option': 'option1'}, function(returnedDataWithOptions) {
//use returnData
//use returnedDataWithOptions
});
});
You should be using callbacks. You also shouldn't be using async: false as this will block the UI thread in browser. You can use jQuery.when() to sync your various tasks. First change your method like this:
function _call(task, opts) {
opts = $.extend({}, opts, options);
return $.ajax({
url: "myphpfile.php",
dataType:"JSON",
type:"POST",
data: { task: task, opts: opts }
});
}
Now you can call it like this:
$.when(
_call("thisismytask"),
_call("thisisanothertask", {'option': 'option1'})
).done(function(firstCallResults, secondCallResults) {
//*CallResults is an array with the following structure: [data, statusText, jqXHR]
var returnedData = firstCallResults[0];
var returnedDataWithOptions = secondCallResults[0];
...
});
This way you keep all the benefits of AJAX asynchrounous nature (those tasks will run in parallel unless server doesn't support it) and get your results back together when they are all available.
I found a solution. #Slavo pointed it out to me.
https://stackoverflow.com/a/6685294/563460
As you're making a synchronous request, that should be
function getRemote() {
return $.ajax({
type: "GET",
url: remote_url,
async: false,
}).responseText; }
Example - http://api.jquery.com/jQuery.ajax/#example-3
Now I just have to decode from text to proper JSON. Thank you everyone. :)
Use jQuery's syntax -
its worked for me,
$.ajaxSetup({async: false});
I'm working on creating a Users collection with the ability to then grab single users inside. This will be used to match from another system, so my desire is to load the users once, and then be able to fine/match later. However, I'm having a problem accessing the outer users collection from an inner method.
function Users(){
var allUsers;
this.getUsers = function () {
// ajax to that Jasmine behaves
$.ajax({
url: '../app/data/jira_users.json',
async: false,
dataType: 'json',
success: function(data) {
allUsers = data;
}
});
return allUsers;
};
this.SingleUser = function (name) {
var rate = 0.0;
var position;
this.getRate = function () {
if(position === undefined){
console.log('>>info: getting user position to then find rate');
this.getPosition();
}
$.ajax({
url: '../app/data/rates.json',
async: false,
dataType: 'json',
success: function(data) {
rate = data[position];
}
});
return rate;
};
this.getPosition = function () {
console.log(allUsers);
//position = allUsers[name];
return position;
};
//set name prop for use later I guess.
this.name = name;
};
}
and the test that's starting all of this:
it("get single user's position", function(){
var users = new Users();
var someone = new users.SingleUser('bgrimes');
var position = someone.getPosition();
expect(position).not.toBeUndefined();
expect(position).toEqual('mgr');
});
The getPosition method is the issue (which might be obvious) as allUsers is always undefined. What I have here is yet another attempt, I've tried a few ways. I think the problem is how the Users.getUsers is being called to start with, but I'm also unsure if I'm using the outer and inner vars is correct.
Though the others are correct in that this won't work as you have it typed out, I see the use case is a jasmine test case. So, there is a way to make your test succeed. And by doing something like the following you remove the need to actually be running any kind of server to do your test.
var dataThatYouWouldExpectFromServer = {
bgrimes: {
username: 'bgrimes',
show: 'chuck',
position: 'mgr'
}
};
it("get single user's position", function(){
var users = new Users();
spyOn($, 'ajax').andCallFake(function (ajaxOptions) {
ajaxOptions.success(dataThatYouWouldExpectFromServer);
});
users.getUsers();
var someone = new users.SingleUser('bgrimes');
var position = someone.getPosition();
expect(position).not.toBeUndefined();
expect(position).toEqual('mgr');
});
This will make the ajax call return whatever it is that you want it to return, which also allows you to mock out tests for failures, unexpected data, etc. You can set 'dataThatYouWouldExpectFromServer' to anything you want at any time.. which can help with cases where you want to test out a few different results but don't want a JSON file for each result.
Sorta-edit - this would fix the test case, but probably not the code. My recommendation is that any time you rely on an ajax call return, make sure the method you are calling has a 'callback' argument. For example:
var users = new Users();
users.getUsers(function () {
//continue doing stuff
});
You can nest them, or you can (preferably) create the callbacks and then use them as arguments for eachother.
var users = new Users(), currentUser;
var showUserRate = function () {
//show his rate
//this won't require a callback because we know it's loaded.
var rate = currentUser.getRate();
}
var usersLoaded = function () {
//going to load up the user 'bgrimes'
currentUser = new users.SingleUser('bgrimes');
currentUser.getRate(showUserRate);
}
users.getUsers(usersLoaded);
your approach to fill the data in allUsers is flawed
the ajax call in jquery is async so every call to users.getAllUsers would be returned with nothing and when later the success function of the jquery ajax is called then allUsers would get filled
this.getUsers() won't work. Its returning of allUsers is independent from the ajax request that fetches the data, because, well, the ajax is asynchronous. Same with getRate().
You'll have to use a callback approach, where you call getUsers() with a callback reference, and when the ajax request completes, it passes the data to the callback function.
Something like:
this.getUsers = function (callback) {
// ajax to that Jasmine behaves
$.ajax({
url: '../app/data/jira_users.json',
async: false,
dataType: 'json',
success: function(data) {
callback(data);
}
});
};
And the call would be along the lines of:
var user_data = null;
Users.getUsers(function(data) {
user_data = data;
});