Here's some pseudo code that I'm working with (and where'd I'd typically use async: false;):
function getData(){
var obj = {};
if(!settings.data){
$.ajax({
url: '/someendpoint',
async: false,
success: function(data){
obj = data;
}
});
} else {
obj = settings.data;
}
return obj;
}
So we have a function that returns an object. Now if we already have some data, we just assign this data, but if we don't, we need to request the data from the server. A time to use async: false? Because we need to halt execution as to grab the data before we return the obj?
I deal with this concept time and time again, resorting to async: false most times. Can someone outline a better approach please?
Embrace the asynchronous nature of the web. Granted, it's paradigm shift, but so was multi-threading back in the day and in my opinion the way it's handled now in jQuery with Deferred objects makes it preferable over multi-threading. It's the reason why Node.js is becoming so popular.
Don't just use callback functions as you might read on the web or in other answers. Learn about Deferred and Promise objects, and instead of returning the data, return a promise that you can use to attach behavior to when that promise is 'resolved'.
function getData(){
var obj;
if(!settings.data){
obj = $.get('/someendpoint');
} else {
obj = $.when(settings.data);
}
return obj;
}
Then, the caller can use this data the moment it becomes available, as such:
var promise = getData();
promise.done(function (data) {
// Do something with the data.
});
It will feel awkward at first, because you're no longer returning data but instead a 'promise' to that data, but after a while you'll just 'get it'. Trust me.
Use a simple callback mechanism
function getData(callback){
var obj = {};
if(!settings.data){
$.ajax({
url: '/someendpoint',
success: function(data){
callback(data)
}
});
} else {
callback(settings.data)
}
}
getData(function(data){
//do things which depends on data
})
Related
I am using jQuery promises to pre-fill the value of a select option. This works fine, but my question is about what to do when an Ajax call is not actually required, so the code does not return a promise.
The code below works fine if this.orgId is not undefined. But when it is not, I get an error:
getFormValues: function() {
var _this = this;
return _this.prefillOrgs()
.then(function(orgId) {
_this.globalOptions.orgId = orgId;
return true;
});
},
prefillOrgs: function() {
if (typeof this.orgId !== 'undefined') {
return $.ajax({
type: 'GET',
url: '/api/1.0/org_code?exact=true&q=' + this.orgId,
dataType: 'json',
context: this
});
} else {
// Doing this produces an error
return [];
}
}
The error returned:
Uncaught TypeError: _this.prefillOrgs(...).then is not a function
I guess this is because returning [] is not a promise and does not have a .then() method defined. However, I would like to return [] as my data payload in this case.
How can I get around this? I'd prefer not to make an unnecessary Ajax call if possible.
UPDATE: The other thing worth noting is that I need the this context inside whatever is returned.
Use $.when
return $.when([]);
It converts a value (or group of values, or a promise) to a promise.
In ES6 the responsibilities are seprate - converting a value or promise to a promise is done with Promise.resolve
If a function is sometimes asynchronous and can return promise than it makes sense to always return promise for consistency. jQuery has $.when() that can create promises from values. So
return [];
will become
return $.when([]);
I have a code as follows:
products.SaveApplications = function (product) {
var promises = [];
ko.utils.arrayForEach(product.Applications(), function (item) {
var applicationToSend = ko.mapping.toJS(item);
promises.push($.ajax({
type: "POST",
contentType: "application/json",
url: serviceUrl + "/InsertApplication",
data: JSON.stringify(applicationToSend),
success: function (data) {
alert("Doing success callback now.");
item.ID(data);
}}));
});
return promises;
}
Then a function products.SaveEnvironments, which is basically the same, except it calls a different function in my controller, and a function products.SaveInstallations, which is, again, mostly the same, except it needs to wait for Environments and Applications to save, because it has some dependant data (Application.ID and Environment.ID).
Now I have a function, which calls both these functions and waits for them until both have finished:
products.SaveChanges = function (product) {
// (...)
var promises = products.SaveApplications(product);
promises = promises.concat(products.SaveEnvironments(product));
$.when(promises).done(function () {
alert("we shouldn't see this before the other message!");
products.SaveInstallations(product);
});
};
The problem is that the alert box with "We shouldn't see this message before the other message!" actually appears before the "Doing success callback now", which means my ID properties are null at the time. It leads me to the conclusion that the $.when call only does the actual promises, but doesn't wait for the success callbacks to finish. How can I make it wait? Thank you.
You are using $.when incorrectly. You can't pass multiple promises to it as an array; if you do that, it treats the array just like any other non-promise value and turns it into a completed promise with the array of the still-pending promises as its result. That, of course, is not useful.
You need to use .apply instead to simulate calling $.when with each promise as a separate argument, like this:
$.when.apply(null, promises).done(function () { ... });
I have this code:
function dialogTexts(message) {
var langText = $.ajax({
type: 'GET',
url: '/index.php/main/dialogtexts',
dataType: 'json'
});
langText.done(function(data) {
message(data);
});
langText.fail(function(ts) {
alert(ts.responseText);
});
}
dialogTexts(function(text) {
alert(text.delete);
});
I'm using a callback to alert the string text.delete (in above code), but I want to do something like:
dialogTexts(function(text) {
return text;
});
var deleteConfirmationDialogText = dialogTexts.delete;
alert(deleteConfirmationDialogText); //deleteConfirmationDialogText is undefined
dialogTexts is a function and it does not have a property .delete. That's why dialogTexts.delete is undefined when you try to use it. You simply can't use the kind of shortcut you're trying to do.
Also, the return statement in here:
dialogTexts(function(text) {
return text;
});
is doing nothing. It's just returning to the caller of that callback which is inside of dialogTexts() and that caller is not doing anything with the return value so return text is doing nothing.
If you want to alert the text, you will have to do what you first had:
dialogTexts(function(text) {
alert(text.delete);
});
That is the only real way to do it and this is a consequence of how asynchronous programming works with AJAX calls. There are some other techniques for handling callbacks (such as promises), but they all require you to process the asynchronously returned data in a callback function. That's just the nature of asynchronous programming.
I'm trying to figure out a good way to check if some asynchronous call is "ready" or not. I have some function that runs $.ajax, and in the callback function sets a boolean variable in the global scope to true (along with some other stuff). Before the ajax call, that boolean variable is false.
I want another function that retrieves that "other stuff." Since the ajax call is asynchronous, clearly I can't just immediately go retrieving it because it probably won't be there yet. That's where this boolean variable comes in. I'm thinking I can just check if that boolean is true every 100ms, and when it is, THEN go retrieve and return the "other stuff".
Code-wise, it looks something like this:
window.FOO = window.FOO || {};
;(function() {
var isReady = false;
var stuff;
$.ajax({
...
success: function(data) {
stuff = data;
isReady = true;
}
})
FOO.getStuff = function() {
// How to check if it's "ready"?
};
}
... (somewhere else)...
var stuff = FOO.getStuff();
I've tried the following for FOO.getStuff to no avail, because (I think) setTimeout is asynchronous:
FOO.getStuff = function() {
if (isReady) {
return stuff;
}
var theStuff;
setTimeout(function() {
theStuff = FOO.getStuff();
}, 100);
return theStuff;
};
Using the console, I can see it doing the right thing... but the first FOO.getStuff call returns before the subsequent ones do.
Is there a better way of doing this?
Edit: To clarify, I want the ajax call to remain asynchronous. I'm perfectly fine with the getStuff() calls being synchronous, because the ajax call will be very fast, and in most cases, getStuff() will be called later (after the used does some things).
Per your comments I have your answer. To solve async problem we should do async actions.
var stuff;
var stuffQueue = [];
$.ajax({
success: function(data) {
stuff = data;
if( stuffQueue.length > 0 ){
for(var i in stuffQueue){
var callback = stuffQueue[i];
callback(stuff);
}
stuffQueue = [];
}
}
});
function getStuff(callback){
//stuff has been loaded?
if( stuff ){
callback(stuff);
}else{
stuffQueue.push(callback);
}
}
To get stuff invoke:
var something = getStuff(function(stuff){
console.log(stuff);
});
This should solve your use case. Let me tell you more info, I have a JavaScript template engine, not yet open source but I have been using in professional projects, and I load all the templates with just one HTTP request, I made that request async:false because it is a dependence of the project.
There are reads that said that async false is evil, I do not believe so, what is evil is to use it wrong. Loading a templates file master, is a good example where async:false could work.
Additional I recommend you to read about promisses:
http://www.html5rocks.com/en/tutorials/es6/promises/
similar idea with Danie Aranda, I'd like to sugget you use custom event.
var isReady = true;
$.ajax({
beforeSend: function() {
isReady = false;
},
success: function(data) {
isReady = true;
$(document).trigger('display-stuff', data);
}
});
Foo.getStuff = function(data) {
if (!isReady) {
$(document).one('display-stuff', Foo.getStuff);
return;
}
// do something
};
I'm having serious problems with the function below:
function requestUploadedSearch()
{
var cookie = JSON.parse(readCookie("user_search_cookie"));
$.ajax({
dataType: "script",
async: false,
data: {
context: "search-get",
code: removeNull(cookie, cookie !== null, "code")
},
success: function(data)
{
var keywords = search_return["keywords"];
return keywords; // here is the damn problem.
}
});
}
Seams that simply nothing comes out of the function except for the undefined value and no errors are shown in the debugger.
And I'm seriously almost throwing my laptop on the wall.
If anyone could help me doing this, please answer!
Thanks in advance.
1st off: Where is the search_return variable? Why are you ignoring data?
I have a feeling this is what you want to do:
function requestUploadedSearch()
{
var cookie = JSON.parse(readCookie("user_search_cookie"));
var keywords;
$.ajax({
dataType: "json",
async: false,
data: {
context: "search-get",
code: removeNull(cookie, cookie !== null, "code")
},
success: function(data)
{
keywords = data["keywords"];
}
});
return keywords;
}
The issue is that since the Ajax call will complete at an arbitrary time in the future, you cannot simply return a value from its success handler.
One issue is that you're not actually doing anything with the data returned by the server, which seems puzzling.
The nutshell version is that you need to implement the functionality as part of the success callback. That can be done in-line, or you can create the callback function outside of the Ajax call itself and use it as the value for the success property:
function onSuccess(data) {
// Do something with the data here
}
...
$.ajax({ // etc.
success: onSuccess
};
You may also use jQuery's $.when function.
The problem is the scope you're trying to return your keywords from. The success function is called by jQuery, and you don't have any control over what jQuery does with that return value. You could do return $.ajax(... but you wouldn't get what you expect, since according to the documentation: "As of jQuery 1.5, the $.ajax() method returns the jqXHR object, which is a superset of the XMLHTTPRequest object" (http://api.jquery.com/Types/#jqXHR).
What you should do instead is set up a callback function like:
function doSomethingWithKeywords(keywords) {
// do stuff
};
and in the success function call that function:
doSomethingWithKeywords(keywords);
EDIT: Hogan's is a good solution, since your call isn't asynchronous.
The problem you are having is the return you are passing is not the return of the function -- it is the return of the event of success. Often closures (implied passing of a local variable to a function) are used to solve this problem in JavaScript.
NB I still don't think your function will work because I don't see where search_return["keywords"] is defined. But at least you won't have to worry about the closure issue. Once your success function is correct the main function will return it.
Like this:
function requestUploadedSearch()
{
var cookie = JSON.parse(readCookie("user_search_cookie"));
var returnClosure;
$.ajax({
dataType: "script",
async: false,
data: {
context: "search-get",
code: removeNull(cookie, cookie !== null, "code")
},
success: function(data)
{
// returnClosure = data["keywords"];
returnClosure = search_return["keywords"];
}
});
return returnClosure;
}