I have only used simple callbacks, i.e. function that performs async AJAX call and calls back once done. Whenever things got anymore complicated I have used $.Deferred(). The problem is that handling promises is a lot of code every time, i would like to know how to use callbacks correctly instead.
Here is my example. (the problem arises in the return from secondary()):
function primary()
{
//call secondary with self as callback
var data = secondary("someStr", primary);
if (data !== false) {
//logic that uses data
}
}
function secondary(str, callback)
{
//call the third function. Since we need parameters
//on the callback, we create anon function
var returnFromThird = tertiary(function() {
secondary(str, callback);
});
if (returnFromThird !== false) {
//when we have data from third do someting....
//but here lies the problem, how do i callback primary()?
return returnFromThird + " " + str;
} else {
//third is not yet ready
return false;
}
}
var myVar3 = null;
function tertiary(callback)
{
if (myVar3 === null) {
//do async ajax call that sets myVar3 value
var ajaxRequest = $.ajax({
url: "/someUrl",
type: "POST",
data: {myData : "blabla"},
async: true,
});
ajaxRequest.done(function(data) {
myVar3 = data;
//issue the call back, so secondary() can come get myVar3
callback();
});
//we did not have the required data so we return false
return false;
} else {
return myVar3;
}
}
//execute
primary();
Here is how i would normally handle the issue using JQuery Deferred:
function primary()
{
var promise = secondary(str);
$.when(promise).then(
function (data) {
//logic that uses data
}
);
}
function secondary(str)
{
var dfd = $.Deferred();
var promise = tertiary();
$.when(promise).then(
function (data) {
dfd.resolve(data + " " + str);
}
);
return dfd.promise();
}
var myVar3 = null;
function tertiary()
{
var dfd = $.Deferred();
if (myVar3 === null) {
var ajaxRequest = $.ajax({
url: "/someUrl",
type: "POST",
data: {myData : "blabla"},
async: true,
});
ajaxRequest.done(function(data) {
myVar3 = data;
dfd.resolve(myVar3);
});
} else {
dfd.resolve(myVar3);
}
return dfd.promise();
}
primary();
If you are using callbacks, you should always call the callback, not sometimes return a value:
var myVar3 = null;
function tertiary(callback) {
if (myVar3 === null) {
//do async ajax call that sets myVar3 value
$.post("/someUrl", {myData : "blabla"}).done(function(data) {
myVar3 = data;
callback(data); // just pass the data to the callback
});
} else {
return callback(myVar3); // always call the callback with the data
}
}
Now your other functions would look like this:
function primary() {
secondary("someStr", function(data) {
//logic that uses data
});
}
function secondary(str, callback) {
tertiary(function(returnFromThird) {
callback(returnFromThird + " " + str);
})
}
But you are right, you should be using promises, it simplifies this a great lot:
var myVarPromise = null;
function tertiary() {
if (myVarPromise === null)
myVarPromise = $.post("/someUrl", {myData : "blabla"});
return myVarPromise;
}
function primary() {
return secondary("someStr").then(function(data) {
//logic that uses data
});
}
function secondary(str) {
return tertiary().then(function(returnFromThird) {
return returnFromThird + " " + str;
});
}
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 6 years ago.
This is my code example
var formValidate = function() {
var url = 'someurl';
var checkC = function (url, callback) {
$.get(url, function( data ) {
if(data.indexOf('OK') == 0) return callback('OK');
})
};
checkC(url, function(data) {
if(data == 'OK') return false;
});
return true;
}
My code is pretty similar to Adam Rackis's in this question Wait for jQuery $.get function to finish before running code. But unfortunately function dont wait for the data return. formValidate() just return true. I want use this function to check some conditions before sending data to server from form
form.on('submit', function(){
if(formValidate()) form_send();
})
Can someone tell me where was I wrong in code above?
The first problem is that formValidate() return true synchronously regardless to the ajax request.
var formValidate = function() {
.
.
.
return true;
}
If you want the to wait for return your values from the callback functions. Here you should call form_send() from the success callback to make it wait for the asynch call. And you can actually make it much simpler without checkC() also. Change the code a bit and use done and fail promises:
var formValidate = function () {
var url = 'someurl';
$.get(url)
.done(function (data) {
if (data.indexOf('OK') == -1) {
console.log("error");
return;
}
form_send();
})
.fail(function () {
console.log("error");
return;
})
}
You can't do this since $.get is called asynchronously.
var formValidate = function() {
var url = 'someurl';
var checkC = function (url, callback) {
$.get(url, function( data ) {
if(data.indexOf('OK') == 0) return callback('OK');
});
};
checkC(url, function(data) {
// this return to this enclosing callback function but not formValidate
if(data == 'OK') return false;
});
//this is the only return statement for formValidate function
return true;
}
A workaround i suggested is just call your form_send() in the callback, for example,
form.on('submit', formValidate);
And for your formValidate,
var formValidate = function () {
var url = "someurl";
$.get(url, function( data ) {
if(data.indexOf('OK') == 0) {
form_send();
}
});
};
I have such function:
$scope.getContactsByScroll = function() {
$scope.pageN = $scope.pageN + 1;
if (!$scope.allDataIsLoaded){
fetchMyDataService.getContactsByScrollService($scope.pageN).then(function(response) {
if (response.length === 0){
$scope.allDataIsLoaded = true;
}
else if (response.length !== 0 && !$scope.allDataIsLoaded){
angular.forEach(response, function(el) {
$scope.contacts.push(el);
});
//$timeout(function() {
$scope.getContactsByScroll();
//}, 2000);
}
}).catch(function() {
$scope.allDataIsLoaded = true;
});
}
};
but it call themselves several times even, if $scope.allDataIsLoaded is false
when i set timeout: all works like a charm. But i don't think that this is a good solution. How can i delay my function without a timeout?
Using timeout in asynchronous functions is not a good idea:
If your request time is longer then timeout, then you'll get unnecessary requests.
If your timeout is bigger then request time, then you'll get unnecessary lags.
You should use promise chain for reqursive requests. Try something like this:
var asyncService = function ($timeout)
{
var someData = 10;
var service =
{
FetchData: function (someArray)
{
someData++;
//timeout here is just for demonstration of async request
return $timeout(function () { return someData }, 1000)
.then(function (result)
{
return service.ProcessData(result, someArray);
});
},
ProcessData: function (data, someArray)
{
console.log(data);
if (data == 15)
{
return someArray;
}
else
{
someArray.push(data)
return service.FetchData(someArray);
}
}
};
return service;
}
Here's a plunker with demonstration
I'm new to this kind of problem in javascript and i can't fix this attempt to wait for an asynchronous call combining Angular promise objects and timeouts.
The function onTimeout seems never execute.
getAsyncContent: function (asyncContentInfos) {
var deferObj = $q.defer();
var promiseObj = deferObj.promise;
asyncContentInfos.promiseObject = promiseObj;
var blockingGuard = { done: false };
promiseObj.then(function () {
blockingGuard.done = true;
});
this.wait = function () {
var executing = false;
var onTimeout = function () {
console.log("******************** timeout reached ********************");
executing = false;
};
while (!blockingGuard.done) {
if (!executing && !blockingGuard.done) {
executing = true;
setTimeout(onTimeout, 200);
}
}
};
$http.get(asyncContentInfos.URL, { cache: true })
.then(function (response) {
asyncContentInfos.responseData = response.data;
console.log("(getAsyncContent) asyncContentInfos.responseData (segue object)");
console.log(asyncContentInfos.responseData);
deferObj.resolve('(getAsyncContent) resolve');
blockingGuard.done = true;
return /*deferObj.promise*/ /*response.data*/;
}, function (errResponse) {
var err_msg = '(getAsyncContent) ERROR - ' + errResponse;
deferObj.reject(err_msg);
console.error(err_msg);
});
return {
wait: this.wait
}
}
Client code is something like this:
var asyncVocabulary = new AsyncContentInfos(BASE_URL + 'taxonomy_vocabulary.json');
getAsyncContent(asyncVocabulary).wait();
And AsyncContentInfos is:
function AsyncContentInfos(URL) {
this.URL = URL;
this.responseData = [];
this.promiseObject;
}
$http.get returns a promise which will resolve when the call completes. Promises are a way to make asyncronous more clean and straight lined than plain old callbacks.
getAsyncContent: function (asyncContentInfos) {
return $http.get(asyncContentInfos.URL, { cache: true })
.then(function (response) {
return response.data;
}, function (errResponse) {
console.error(err_msg);
throw errResponse;
});
}
Then using it:
getAsyncContent({...}).then(function(yourDataIsHere) {
});
The nice thing about promises is that they can be easily chained.
getAsyncContent({...})
.then(function(yourDataIsHere) {
return anotherAsyncCall(yourDataIsHere);
})
.then(function(your2ndDataIsHere) {
});
My foreach loop:
jQuery(".custom-checkbox").each(function() {
if (jQuery(this).attr('data-action') == 'true') {
if(deleteQuoteItemFromListing(jQuery(this).attr('data-id'))){
console.log('passed');
}else{
console.log('failed');
}
}
});
And the function is(It's using prototype) but it successes
function deleteQuoteItemFromListing(id){
//does someoperations and on success
delurl = getDelUrl()+id; //baseurl/module/action/delete/id
new Ajax.Request(delurl,{
method: 'get',
onSuccess: function(transport){
return TRUE;
}
})
}
but the problem is all foreach executes at once, and doesn't wait for response from function. It prints failed even the operation is success.
Updated
The other way round i tried first is this
jQuery('.delete-from-quote').click(function() {
var i = 0, j = 0;
jQuery(".custom-checkbox").each(function() {
if (jQuery(this).attr('data-action') == 'true') {
i++;
}
});
if (i == 0) {
alert('please choose product');
return false;
}
jQuery(".custom-checkbox").each(function() {
if (jQuery(this).attr('data-action') == 'true') {
var urlData = "<?php echo $this->getUrl('qquoteadv/index/delete/'); ?>";
urlData += "id/" + jQuery(this).attr('data-id') + "/"
var ajax = jQuery.ajax({
type: "GET",
url: urlData,
success: function(msg) {
j++;
}
})
}
if(i==j){location.reload();} //after completing all, reload the page
});
});
The problem is to know all action completed and reloading the page.
My guess is that the code you've omitted is doing an asynchronous ajax call. Since ajax is asynchronous by default, the code you write there ($.ajax or whatever) starts the process, but then the process continues in the background while your code continues to run.
There's no reasonable way to make the deleteQuoteItemFromListing function wait for the response. (While it's possible to do synchronous ajax, A) it makes for a poor user experience by locking up the browser UI, and B) jQuery will be removing that option at some stage, forcing you to go direct to XHR if you want to keep doing it.)
Instead, restructure your code to embrace the asynchronous nature of web programming by having your function either return a promise or accept a callback, and then resolve the promise or call the callback when done.
Here's a rough idea of what the promise version would look like:
jQuery(".custom-checkbox").each(function() {
if (jQuery(this).attr('data-action') == 'true') {
deleteQuoteItemFromListing(jQuery(this).attr('data-id'))
.done(function(id) {
console.log(id + ' passed');
})
.fail(function(id) {
console.log(id + ' failed');
});
}
});
function deleteQuoteItemFromListing(id){
var d = jQuery.Deferred();
jQuery.ajax(/*...*/)
.done(function() { // This bit assumes the deletion worked if
d.resolveWith(id); // the ajax call worked, and failed if the
}) // ajax call failed; if instead the ajax
.fail(function() { // call always works and returns a flag,
d.rejectWith(id); // adjust accordingly
});
return d.promise();
}
Using callback ensures that the function is executed.
jQuery(".custom-checkbox").each(function () {
if (jQuery(this).attr('data-action') == 'true') {
deleteQuoteItemFromListing(jQuery(this).attr('data-id'), handleData);
}
});
function handleData(data) {
if (data) {
console.log('passed');
} else {
console.log('failed');
}
}
function deleteQuoteItemFromListing(id, callback) {
//does someoperations and on success
delurl = getDelUrl() + id; //baseurl/module/action/delete/id
new Ajax.Request(delurl, {
method: 'get',
onSuccess: function (transport) {
callback(true);
}
})
}
I hope this will work for you. you need to define handleData function outside of the other function.
Use jquery When.
You need to queue those Deferred in an array of Deferred and then apply all of the functions at once.
If one fails all will fail and if all succeeds all will pass.
check this out jQuery When
var queue = [];
var items = 0;
return new $.Deferred(function (deferred) {
$(".custom-checkbox").each(function () {
if ($(this).attr('data-action') == 'true') {
items++;
queue.push(function () {
new Ajax.Request(delurl, {
method: 'get',
onSuccess: function (transport) {
items--;
if(items === 0)
deferred.resolve();
},
onError:function(e){
deferred.reject(e);
}
});
});
}
});
//now resolve all of the deferred fns
$.when(queue).done(function(){
console.log('All went well');
})
.fail(function(e){
console.log('Error ' + e);
});
});
(Part of) Your problem is in this simple statement:
return TRUE;
In JavaScript, the "true" boolean is written in lowercase:
return true;
The interpreter thinks TRUE is a variable, and will throw a ReferenceError, since it's not set / defined anywhere, meaning the function will never return true.
function lookupRemote(searchTerm)
{
var defaultReturnValue = 1010;
var returnValue = defaultReturnValue;
$.getJSON(remote, function(data)
{
if (data != null)
{
$.each(data.items, function(i, item)
{
returnValue = item.libraryOfCongressNumber;
});
}
});
return returnValue;
}
Why is the returnValue from this function alway equal to the default value set at the beginning of the function and never to the value retrieved from the JSON lookup?
If you don't want to use asynchronous function, better use the following:
function getValue(){
var value= $.ajax({
url: 'http://www.abc.com',
async: false
}).responseText;
return value;
}
This function waits until the value is returned from the server.
This happens because that callback function (function(data) {...}) runs later when the response comes back...because it's an asynchronous function. Instead use the value once you have it set, like this:
function lookupRemote(searchTerm)
{
var defaultReturnValue = 1010;
var returnValue = defaultReturnValue;
$.getJSON(remote, function(data) {
if (data != null) {
$.each(data.items, function(i, item) {
returnValue = item.libraryOfCongressNumber;
});
}
OtherFunctionThatUsesTheValue(returnValue);
});
}
This is the way all asynchronous behavior should be, kick off whatever needs the value once you have it...which is when the server responds with data.
The function you pass to getJSON is run when the response to the HTTP request arrives which is not immediately.
The return statement executes before the response, so the variable hasn't yet been set.
Have your callback function do what needs doing with the data. Don't try to return it.
const getJson = (path) => {
return new Promise((resolve) => {
$.getJSON(path, function (data) {
setTimeout(() => {
resolve(data);
}, 1);
});
})
}
var result = await getJson('test.json');
console.log(result);