Jquery When and Deferred object, broken function flow - javascript

I am using $.when and .done to make sure that the close window happens after the data is saved. But, this doesn't seem to work as expected.
The workflow is that, user clicks on a button "Save and Close", which should save the data first, trigger print and close the window. But the save data and close window happens at the same time which makes the print fail.
I have read about when..then and deferred object. Tried to implement it here the following code, sometimes it work but most of the time it would break.
$("#btnSaveAndClose").click(function (event) {
$.when(zSaveSomeData()).done(function (value) {
zCloseMyWindow();
});
});
function zSaveSomeData() {
return zSaveMasterData(masterdata, function () {
return zSaveDetailData();
});
};
function zSaveMasterData(masterdata, fnAfterSave) {
return $.ajax({
type: 'POST',
contentType: 'application/json',
url: '/api/masterdata/',
data: JSON.stringify(masterdata),
success: function (data) {
fnAfterSave();
}
});
};
function zSaveDetailData() {
var selectedDataGroups;
// some logic here
zSaveDetails(selectedDataGroups);
};
function zSaveDetails(selectedDataGroups) {
var deferred = $.Deferred();
$.ajax({
type: 'POST',
contentType: 'application/json',
url: '/api/detaildata/',
data: JSON.stringify(selectedDataGroups),
success: function (data) {
var printableGroupIDs = [];
$.each(data, function () {
if (this.IsPrintable)
printableGroupIDs.push(this.ID);
});
if (printableGroupIDs.length > 0) {
zPrintGroups(printableGroupIDs);
}
deferred.resolve('done');
}
});
zAuditSave();
return deferred.promise();
};
function zPrintGroups(newGroupIDs) {
// calls external program to print groups
};
function zCloseWindow() {
window.close();
};
function zAuditSave() {
$.ajax({
type: 'POST',
contentType: 'application/json',
url: '/api/audit'
success: function (data) {
}
});
};
Only thing is that the save calls other methods inside to same master and details data. There are couple of ajax calls too. An unusual thing is that after the data is saved, there is a call to VB code that actually triggers a Print. I am so confused on why would close window fire before the other methods are executed. Any help would be appreciated.

For me the code is overly divided into functions, with some doing little more than fronting for others.
I would prefer to see the click handler as a comprehensive master routine which sequences three promise-returning functions zSaveMasterData(), zSaveDetails() and zAuditSave(), then closes the window. Thus, some of the current functions will be subsumed by the click handler.
$("#btnSaveAndClose").click(function(event) {
zSaveMasterData(masterdata).then(function() {
var selectedDataGroups;
/* some logic here */
var detailsSaved = zSaveDetails(selectedDataGroups).then(function(data) {
var printableGroupIDs = $.map(data, function (obj) {
return obj.IsPrintable ? obj.ID : null;
});
if (printableGroupIDs.length > 0) {
// calls external program to print groups
}
});
// Here, it is assumed that zSaveDetails() and zAuditSave() can be performed in parallel.
// If the calls need to be sequential, then the code will be slightly different.
return $.when(detailsSaved, zAuditSave());
}).then(function() {
window.close();
});
});
function zSaveMasterData(masterdata) {
return $.ajax({
type: 'POST',
url: '/api/masterdata/',
contentType: 'application/json',
data: JSON.stringify(masterdata),
});
};
function zSaveDetails(selectedDataGroups) {
return $.ajax({
type: 'POST',
contentType: 'application/json',
url: '/api/detaildata/',
data: JSON.stringify(selectedDataGroups)
});
};
function zAuditSave() {
return $.ajax({
type: 'POST',
contentType: 'application/json',
url: '/api/audit'
});
};
Note the returns in the three functions with ajax calls. These returns are vital to the sequencing process.
A potentially bigger issue, not addressed in the question (nor in this answer) is how to recover from errors. Presumably, the database will be inconsistent if the sequence of saves was to fail part way through. It may well be better to ditch this client-side sequencing approach in favour of a server-side transaction that the client sees as a single operation.

The problem here is your code doesn't depend on when fnAfterSave() has completed.
Short answer: don't mix success methods, callbacks, and promises - use one pattern and stick to it - and the easiest pattern to use is promises.
$("#btnSaveAndClose").click(function (event) {
zSaveSomeData().then(function() { zCloseMyWindow(); });
});
function zSaveSomeData() {
return zSaveMasterData(masterdata).then(function(data) { zSaveDetailData() });
};
function zSaveMasterData(masterdata) {
return $.ajax({
type: 'POST',
contentType: 'application/json',
url: '/api/masterdata/',
data: JSON.stringify(masterdata)
});
//remove success callback here as it breaks the chaining
};

It seems like your problem is that you are doing asynchronous things inside an ajax success callback. The promise returned by $.ajax still resolves immediately after the response is received - and executes your done callback before the asynchronous zSaveDetailData() has finished.
So, to chain asynchronous actions, always use then. Use it even for synchronous actions, it makes the sequence clear.
Don't use success callbacks when you're working with promises. You also don't need deferreds. You might want to have a look at these generic rules as well, especially that you never must forget to return promises from async functions that you want to await.
$("#btnSaveAndClose").click(function (event) {
zSaveSomeData().then(zCloseMyWindow);
});
function zSaveSomeData() {
return zSaveMasterData(masterdata).then(zSaveDetailData);
}
function zSaveMasterData(masterdata) {
return $.ajax({
type: 'POST',
contentType: 'application/json',
url: '/api/masterdata/',
data: JSON.stringify(masterdata),
});
}
function zSaveDetailData() {
var selectedDataGroups;
// some logic here
return zSaveDetails(selectedDataGroups);
// ^^^^^^
}
function zSaveOrderGroups(selectedDataGroups) {
return $.ajax({
// ^^^^^^
type: 'POST',
contentType: 'application/json',
url: '/api/detaildata/',
data: JSON.stringify(selectedDataGroups)
}).then(function(data) {
// ^^^^^^^^^^^^^^^^^^^^^^
var printableGroupIDs = [];
$.each(data, function () {
if (this.IsPrintable)
printableGroupIDs.push(this.ID);
});
if (printableGroupIDs.length > 0) {
return zPrintGroups(printableGroupIDs);
// ^^^^^^
}
}).then(zAuditSave);
// ^^^^^^^^^^^^^^^^^
}
function zPrintGroups(newGroupIDs) {
// calls external program to print groups
}
function zCloseWindow() {
window.close();
}
function zAuditSave() {
return $.ajax({
// ^^^^^^
type: 'POST',
contentType: 'application/json',
url: '/api/audit'
});
}

Related

How to handle this with async Ajax request and callback

So this function getXmlData() get's called across the app who's main responsibility is to return xml to and save it to a variable, in this example below it is 'test'
var test = getXmlData();
function getXmlData() {
queryData(getData);
}
function getData(xml) {
if (xml) {
return xml;
}
}
function queryData(callback){
$.ajax({
url: "/echo/JSON",
type: 'POST',
success: function(xml){
callback(xml);
},
error: function(){
console.log("Error!!");
}
})
}
Now we know that here parent function will return before the callback is executed and so 'test' variable will be undefined.
I'm not sure how can I handle this situation here. I'm tied to using this structure. I basically want getXmlData to return xml that is being returned in queryData. Suggestions!?

AJAX Call that Returns Javascript Variable

Struggling to return a AJAX Result Variable back to JavaScript
Note that the $.ajax call below is synchronous (async: false).
Ajax Call
function getState(callback) {
$.ajax({
url: 'getSearchState.php',
data: { "state": callback },
type: 'GET',
async: false,
success: function(result){
alert(result);
},
error: function(result) {
alert(result);
}
});
}
Ajax PHP
<?php
// Database Setup and Query
while ($row = $xxxxx->fetch(PDO::FETCH_ASSOC)) {
$StateVal = $row['State'];
}
return $StateVal;
?>
Javascript Calling the Function
var URL = District.trim();
var StateURL = getState(URL);
It gets the URL vairable from the function just fine, but doesnt return anything.
Any help would be great!
There are problems with that code both client-side and server-side.
Client-side:
Your getState is never returning anything, so it's no surprise that you don't see anything other than undefined for StateURL.
Don't use synchronous ajax. It makes for horrible UX. But if you really, really want to keep using it, here's how you would:
function getState(state) {
var result; // <=== Where we'll put our result
$.ajax({
url: 'getSearchState.php',
data: {"state": state},
type: 'GET',
async: false,
success: function(data) {
// Remember the result;
result = data;
},
error: function() {
result = /*...whatever you want to use to signal an error */;
}
});
// Return the result
return result;
}
Note that I changed the name of the argument to state, since it's not a callback.
But again, don't use synchronous ajax. Instead, use a callback or promises.
Promise: $.ajax already returns a promise, so just return that directly:
function getState(state) {
var result; // <=== Where we'll put our result
$.ajax({
url: 'getSearchState.php',
data: {"state": state},
type: 'GET',
async: false,
success: function(data) {
// Remember the result;
result = data;
},
error: function() {
result = /*...whatever you want to use to signal an error */;
}
});
// Return the result
return result;
}
Note that I changed the name of the argument to state, since it's not a callback.
But again, don't use synchronous ajax. Instead, use a callback or promises.
Promise:
function getState(state) {
return $.ajax({
url: 'getSearchState.php',
data: {"state": state},
type: 'GET'
});
}
Usage:
getState(URL)
.done(function(StateURL) {
// Use it
})
.fail(function() {
// Failed
});
Callback:
function getState(state, callback) {
$.ajax({
url: 'getSearchState.php',
data: {"state": state},
type: 'GET',
success: function(data) {
// Call the callbback with the result
callback(data);
},
error: function() {
// Call the callback with an error
callback(/*...whatever you want to use tosignal an error */);
}
});
}
Usage:
getState(URL, function(StateURL) {
// Use it, check for error
});
Server-side:
As RiggsFolly pointed out, you're returning a string from your PHP code. But that won't output it. To use it client-side, you need to output it (e.g., echo and similar). And to make it easily consumed by the JavaScript, you probably want to json_encode it to ensure that it's in a format JavaScript can understand:
echo json_encode($stateVal);
Then in your success (or done) function, use JSON.parse on it:
result = JSON.parse(data);
this is jQuery and in this case you can specify context and in success function set variables on that context.... a bit crude solution but it will works. Also take a look on arrow functions and promises from ES6, it can help you a lot and give you new perspective about whole problem.
And one main thing!! Ajax is async by default so you need somehow notify your StateURL when data will be ready (here again promise at you service)

Return value from ajax call

I'm trying to get the return bool from the ajax call, but when I run the function and save to a variable but the only thing i get is undefined.
function check(number, id){
$.ajax({
url: 'ai.php',
data: {
"func":"checkNumber",
"id":id,
"number":number
},
method: 'GET',
dataType: 'json'
}).done(function(jData) {
return jData['idFound'];
}).fail(function(jData) {
console.log(jData['idFound']);
});
}
var test = check(44444, 12);
console.log(test);
// true
// but i get an undefined value in the console.log
Depend ur file 'ai.php' is a format json? please check validate http://www.jsonlint.com
Confuse ur code data, its just 'post' and not 'get'.
You can try getjson or ajax, look a exemple:
$(document).ready(function() {
$.getJSON('http://beta.json-generator.com/api/json/get/NkmG8_zBZ', function(json) {
for (x=0; x<json.length; x++) {
console.log('getjson>'+json[x].nome);
}
});
$.ajax({
type: 'get',
url: 'http://beta.json-generator.com/api/json/get/NkmG8_zBZ',
dataType : 'json',
success: function(json) {
for (x=0; x<json.length; x++) {
console.log('ajax>'+json[x].cidade);
}
},
error: function(err) {
console.log(err);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
Without playing with the code more, this looks like an asynchronous issue to me.
This line calls the async function:
var test = check(44444, 12);
but logging the result immediately after will be undefined because you can't guarantee a return value unless you are within the 'done' callback function.
The default JQuery AJAX behavior is asynchronous, but if you truly need it to be synchronous, you can set the option explicitly, although it is not considered good practice:
$.ajax({
async: false,
...
});

How to use requirejs to work with ajax callbacks?

If I have to leverage niceties of jQuery AJAX API and set my own custom settings for each ajax call my app makes like below:
Say I have a page which displays employee information within table by making ajax calls to some API.
define(["jQuery"], function($) {
var infoTable = function (options) {
function init() {
// Provide success callback
options.success_callback = "renderData";
getData();
}
function renderData() {
// This callback function won't be called as it is not
// in global scope and instead $.ajax will try to look
// for function named 'renderData' in global scope.
// How do I pass callbacks defined within requirejs define blocks?
}
function getData() {
$.ajax({
url: options.apiURL,
dataType: options.format,
data: {
format: options.format,
APIKey: options.APIKey,
source: options.source,
sourceData: options.sourceData,
count: options.count,
authMode: options.authMode
},
method: options.method,
jsonpCallback: options.jsonpCallback,
success: options.success_callback,
error: options.error_callback,
timeout: options.timeout
});
}
}
return {
init: init
}
}
How do I achieve this?
I know we can use JSONP request as require calls but that restricts me to using jsonp, making GET requests and all other features $.ajax offers.
This example would let you either use a default success callback, or provide an override, using:
success: options.successCallback || renderData
(The example uses jsfiddle rest URLs - this fact is unimportant, and stripped out the data object to keep the example short)
define("mymodule", ["jquery"], function($) {
function renderData() {
console.log("inside callback");
}
function getData(options) {
$.ajax({
url: options.apiURL,
dataType: options.format,
method: options.method,
jsonpCallback: options.jsonpCallback,
success: options.successCallback || renderData,
error: null,
timeout: options.timeout
});
}
return {
getData: getData
}
});
require(["mymodule"], function(m) {
console.log(m, m.getData({
apiURL: "/echo/json/"
}));
console.log(m, m.getData({
successCallback: function() { console.log("outside callback"); },
apiURL: "/echo/json/"
}));
});
Would print:
GET http://fiddle.jshell.net/echo/json/ 200 OK 263ms
Object { getData=getData()} undefined
GET http://fiddle.jshell.net/echo/json/ 200 OK 160ms
Object { getData=getData()} undefined
inside callback
outside callback

jQuery's AJAX call to a javascript class method

I'm a newbee about jQuery's workflow and I would like to setup a javascript class that uses an internal method to make an AJAX request. When the request returns with success, the jQuery AJAX callback should invoke a method owned by the class itself. That's the code:
function IXClock()
{
this.m_intervalID = 0;
this.startClock = function ()
{
this.m_intervalID = setInterval(this.tictac, 500);
}
this.stopClock = function ()
{
clearInterval(this.m_intervalID);
}
this.setClockTime = function(p_strTime)
{
$('#clock').html(p_strTime);
}
this.tictac = function ()
{
$.ajax
({
type: 'POST',
url: '/rap/rapClock.php',
complete: function (data)
{
this.setClockTime(data);
}
});
}
}
The class represents a clock, with an internal method (tictac) that requests "what's the time" on the server side.
After the server says the time, the jQuery's AJAX method should invoke the setClockTime method of the IXClock class. The invoke method will update the #clock div item in the html page.
The problem is that the method this.setClockTime() results unknown and the javascript return the "this.setClockTime is not a function" error.
The question is: is there a way to invoka a class method from the jQuery's AJAX callback ?
I think that the problem is that the this in your callback function is different from the this referring to IXClock. Try:
var thisClass = this ;
this.tictac = function ()
{
$.ajax
({
type: 'POST',
url: '/rap/rapClock.php',
complete: function (data)
{
thisClass.setClockTime(data);
}
});
}
Test Case (added to site which already has jQuery loaded):
function uClass () {
this.testFunction = function(input) {
alert(input) ;
}
this.ajaxFunction = function() {
var myClass = this ;
$.ajax({
type: 'POST',
url: '/',
complete: function(data) {
alert(myClass.testFunction) ;
myClass.testFunction(data) ;
this.testFunction(data) ;
}
}) ;
}
}
var k = new uClass() ;
k.ajaxFunction() ;
It happens bacause your callback function leave in global context.
You can choose 2 ways
Use .bind function to bind context to callback function http://www.robertsosinski.com/2009/04/28/binding-scope-in-javascript/
jQuery's AJAX supports transfer some data to callback function. You can write smth like this:
:
this.tictac = function () { $.ajax ({ type: 'POST', context:this, url: '/rap/rapClock.php', complete: function (data) { this.setClockTime(data); } }); }
}
this does not refer to IXClock in your ajax callback. this allways points to the current scope (have a look at this document). You need to do something like this:
this.prototype.tictac = function ()
{
var self = this;
$.ajax
({
type: 'POST',
url: '/rap/rapClock.php',
complete: function (data)
{
self.setClockTime(data);
}
});
}
You can also use jQuery's .proxy()-function for this purpose:
this.prototype.tictac = function ()
{
$.ajax
({
type: 'POST',
url: '/rap/rapClock.php',
complete: $.proxy(function (data) {
this.setClockTime(data);
}, this)
});
}
The this in the result handler is not what you expect it is. (It is not the IXClock instance)
function IXClock()
{
this.m_intervalID = 0;
}
IXClock.prototype = {
startClock: function ()
{
this.m_intervalID = setInterval(this.tictac, 500);
},
stopClock: function ()
{
clearInterval(this.m_intervalID);
},
setClockTime: function(p_strTime)
{
$('#clock').html(p_strTime);
},
tictac: function ()
{
var that = this;
$.ajax({
type: 'POST',
url: '/rap/rapClock.php',
success: function (data) { // You want success here, not complete, IMO
that.setClockTime(data);
}
});
}
}
If you ask me, that ajax call is doing evil. It does not seem to send any data, nor modify any
state on the server, but is expecting/getting/using data from the php, yet is using the POST method.
Should've been
$.get('/rap/rapClock.php', function (data) {
that.setClockTime(data);
});
One simple solution is, to keep your callback function as self = this. This will support inheritance also.
class Record{
get_data(){
self = this;
$.ajax({
type : "GET",
url : "/get_url",
dataType : "json",
contentType: "application/json; charset=utf-8",
data : {},
success : function(data){
console.log(data);
self.load_table(data);
},
});
}
static load_table(data){
console.log(data);
}

Categories