Can we make a callback in a chain like this?
Widget.update(...).onUpdate(function(data){
console.log('updated');
});
current code,
var Gateway = {};
Gateway.put = function(url, data, callback) {
$.ajax({
type: "POST",
dataType: "xml",
url: url,
data: data,
async: true,
success: function (returndata,textStatus, jqXHR) {
callback(returndata);
}
});
};
var Plugin = function() {};
Plugin.prototype = {
update : function(options, callback) {
/// other stuff
Gateway.put($url, $data, function(data){
callback(data);
}
return this;
}
}
usage,
var Widget = new Plugin();
Widget.put({
button: options.button
}, function(data){
console.log('updated');
});
but ideally,
Widget.update(...).onUpdate(function(data){
console.log('updated');
});
EDIT:
at jsfiddle.
What you are trying to do will work however you need to pass your callback to update
Widget.update(yourOptions, function(data){
console.log('updated');
});
You could also return your ajax request directly and chain onto it
var Gateway = {};
Gateway.put = function(url, data) {
return $.ajax({
type: "POST",
dataType: "xml",
url: url,
data: data,
async: true
});
};
var Plugin = function() {};
Plugin.prototype = {
update : function(options) {
/// other stuff
return Gateway.put($url, $data);
}
}
var Widget = new Plugin();
Widget.update(yourOptions).done(function() {
console.log('updated');
});
I really like the callback hell coding style, but sometimes it hurts. As suggested by other users, have you already heard about promises?
The core idea behind promises is that a promise represents the result of an asynchronous operation.
As suggested in the link above - that proposed a standard for them - once polyfill'd the browser using
<script src="https://www.promisejs.org/polyfills/promise-done-6.1.0.min.js"></script>
you will be able to create new Promise's, hence to use their nice done() attribute.
You will end up with
Plugin.prototype.update = function (options) {
return new Promise(function (fullfill, reject) {
Gateway.put($url, $data, function (data) {
fullfill(data);
});
});
};
That is, Plugin.prototype.update returns a promise.
Widget.update(...).done(function(data){
console.log('updated');
});
I have not tested the code, but the spirit is that. :)
EDIT: Using promises is awesome. I simply don't like when people discover them, use them in newer parts of the codebase but finally do not refactor the rest of it.
Related
I have a method that is called in several places in the project. I've done method. the first method call do Ajax get, cache data in class property and fire callback. Second call method only call callback with cached data. I would like to add the ability to load data synchronously. Date should be returned by the method. I added an additional parameter to call {async: false}, but I wonder if there is a better solution using ES7 promises?
This is my callback solutions.
export class loadData {
constructor() {
this.data = [];
}
getData({callback, async = true}){
let syncData = this.data;
if( this.data.length === 0 ){
$.ajax({
beforeSend: authorizationManager.addAuthorizeHeader(),
url: apiUrl + '/Data/datadata',
dataType: 'json',
cache: true,
async: async
}).done((data)=>{
if(async) callback(data);
this.data = data;
syncData = data;
});
} else {
if(async) callback(this.data);
}
if(async === false) return syncData;
}
}
loadDataTest = new loadData();
call async
loadDataTest.getData({
callback: (data) =>{
console.log(data);
}
});
call sync
let a = loadDataTest.getData({
async: false
});
Promises are almost always the better solution. Of course they are never synchronous, but that's usually the better solution as well. This is how it would look like:
export class loadData {
constructor() {
this.promise = null;
}
getData() {
if (this.promise == null) {
this.promise = Promise.resolve($.ajax({
beforeSend: authorizationManager.addAuthorizeHeader(),
url: apiUrl + '/Data/datadata',
dataType: 'json',
cache: true
}));
}
return this.promise;
}
}
And the call:
loadDataTest.getData().then((data) => {
console.log(data);
});
I would like to add the ability to load data synchronously
I don't think you really want that. If all you want is synchronous-looking syntax for asynchronous functionality, have a look at async/await.
I have two buttons that both performs AJAX call:
$("#save").click(function() {
$.ajax({
type: "POST",
url: saveEntryURL,
data: { id: $("#id").val() },
success: function(r) {
...
},
error: function(r) {
...
}
})
})
$("#tag-as-final").click(function() {
$.ajax({
type: "POST",
url: finalizeEntryURL,
data: { id: $("#id").val() },
success: function(r) {
...
},
error: function(r) {
...
}
})
})
The requirement is that when the user click the finalize button, the system will first perform a save before actually tagging it as final. To reuse the code attached to the save button, I call the onclick listener of the save button before the actual AJAX call like this:
$("#tag-as-final").click(function() {
$("#save").click()
^^^^^^^^^^^^^^^^^^
$.ajax({
type: "POST",
url: finalizeEntryURL,
But it will not do "save-and-finalize-after" behavior since both AJAX calls are asynchronous. I need to run one after another, but cannot afford to make the AJAX call of the save button synchronous (I'm doing also a lot of other things while the tagging occurs occurs). I know this would be silly but I'm thinking something similar to...
$("#tag-as-final").click(function() {
$("#save").click().peformAsyc()
^^^^^^^^^^^^
$.ajax({
type: "POST",
url: finalizeEntryURL,
...that will force it to finish performing first the chained function before continuing, but I know that is not available. Is there any way to do this? My current work-around is placing the same save AJAX function inside the finalize AJAX function, though it doesn't allow me to code DRY (Don't Repeat Yourself):
$("#tag-as-final").click(function() {
$.ajax({
type: "POST",
url: saveEntryURL,
data: { id: $("#id").val() },
success: function(r) {
...
$.ajax({
type: "POST",
url: finalizeEntryURL,
data: { id: $("#id").val() },
success: function(r) {
...
},
error: function(r) {
...
}
})
},
error: function(r) {
...
}
})
})
It's pretty simple, you are better using jquery "promises". Like so:
var generalSettings = { }; //Settings for AJAX call.
var jqXHR = $.ajax(generalSettings); //Do AJAX call.
generalSettings.data = 'newdata'; //update generalSettings
jqXHR.done(function(data){
$.ajax(generalSettings); //New Petition with updated settings.
});
This is using ES6 promises and jQuery promises:
function doAjaxAsPromise(settings){
return new Promise(function(resolve){
var jqXHR = $.ajax(settings);
jqXHR.done(function(data){
resolve(data);
});
});
}
var settings = { };
var petition = doAjaxAsPromise(settings);
var secondpetition = petition.then(function(data){
//work with data
//new settings
var settings = { };
return doAjaxAsPromise(settings);
});
var thirdpetition = secondpetition.then(function(data){
//work with data
//new settings
var settings = { };
return doAjaxAsPromise(settings);
});
//If needed to reuse settings object outside promise scope:
//var settings = Object.create(settings);
Some other nice thing you can do for code reuse:
function save(settings) {
var prom = doAjaxAsPromise(settings);
return prom.then(function(data){
//do something with your data.
});
}
function tagAsFinal(savedPromise, settings){
return savedPromised.then(function(){
var prom = doAjaxAsPromise(settings);
return prom.then(function(data){
//work with data;
});
});
}
$('save').on('click', function(){
save(settings); //settings = $.ajax settings.
});
$('tagAsFinal').on('click', function(){
var generalSettings = { };
var settingsone = Object.create(generalSettings);
var settingstwo = Object.create(generalSettings);
var saved = save(settingsone); //$.ajax settings.
tagAsFinal(saved, settingstwo);
});
//Can still be reduced.
I have the following code:
var User = {
get: function (options) {
var self = this;
$.ajax({
url: options.url,
success: function (data, response) {
self.nextPageUrl = data.pagination.next_page;
options.success(data, response);
}
});
},
nextPage: function (success) {
this.get({
url: this.nextPageUrl,
success: success
});
}
}
User.get({
url: 'https://cache.getchute.com/v2/albums/aus6kwrg/assets',
success: function (data, response) {
// Through `data.pagination.next_page` I can get the URL
// of the next page.
}
});
User.nextPage({
success: function (data, response) {
// Here I want to make the same request but using the next_page
// based on the next related to the previous' one.
}
});
The problem
Basically, I want to perform the nextPage() operation based on the antecessor request (User.get()), but due to its asynchronousity, the nextPage() method doesn't know the this.nextPageUrl property—it returns undefined as expected.
Finally, the question is: can someone think in a way to keep the current syntax flow but solving this approach? Actually, is there a way?
And no, I'm not available to make a synchronous request.
General knowledge
I thought to use an event mechanism to deal with this: when the request is made and .nextPage() is called, then try to listen to an event to be emitted for x seconds, then I expected the this.nextPageUrl property to be available in that event-based scope.
What do you guys think?
DISCLAIMER: The logic of next_page is preprocessed by the server and only then is sent to the client. I have no option to use an increment/decrement behavioral operation.
If you want to play with this problem, click here for the jsFiddle.
jsFiddle Demo
There are a couple of options. You could bind the setter of the property to also call the nextPage, you could poll from calling nextPage every n milliseconds until the nextPageUrl property was populated, you could use a promise, you could use a nextPageQueue.
I think that a queue may be the simplest form of completing this. I also think it may be useful to have User store some local variables in this situation, and that the use of a function object may be more inline with that.
It would look like this
var User = new function(){
var pageQueue = [];
var get = this.get = function (options) {
$.ajax({
url: options.url,
dataType: 'JSON',
success: function (data, response) {
options.success(data, response);
var nextPageUrl = data.pagination.next_page;
if( pageQueue.length > 0 ){
pageQueue[0](nextPageUrl);
pageQueue.splice(0,1);
}
}
});
};
var nextPage = this.nextPage = function (options) {
pageQueue.push(function(nextPageUrl){
get({
url: nextPageUrl,
success: options.success
});
});
};
};
and your calls would not change.
User.get({
url: 'https://cache.getchute.com/v2/albums/aus6kwrg/assets',
success: function (data, response) {
// Through `data.pagination.next_page` I can get the URL
// of the next page.
console.log('get');
console.log(data);
console.log(response);
}
});
User.nextPage({
success: function (data, response) {
// Here I want to make the same request but using the next_page
// based on the next related to the previous' one.
console.log('next');
console.log(data);
console.log(response);
}
});
You can grab a reference to your User object before making the asynchronous request.
var User = {
get: function (options) {
var self = this;
$.ajax({
url: options.url,
success: function (data, response) {
self.nextPageUrl = data.pagination.next_page;
options.success(data, response);
}
});
},
You can modify your get method and eliminate your nextPage method entirely:
var User = {
url: 'https://cache.getchute.com/v2/albums/aus6kwrg/assets',
get: function (options) {
var self = this;
self.pageXhr = $.ajax({
url: self.nextPageUrl ? self.nextPageUrl : self.url,
dataType: 'JSON',
success: function (data, response) {
self.nextPageUrl = data.pagination.next_page;
self.pageXhr = false;
options.success(data, response);
}
});
}
}
Then whenever you call User.get, it will either call the current page or the next page. I am not sure about the context of when you want to get the subsequent pages, but if you need the requests to be queued you can wait for the existing request to finish before triggering the next request. For example:
if (self.pageXhr) {
self.pageXhr = self.pageXhr.then(function() {
return $.ajax({
url: self.nextPageUrl ? self.nextPageUrl : self.url,
dataType: 'JSON',
success: function (data, response) {
self.nextPageUrl = data.pagination.next_page;
options.success(data, response);
}
});
});
}
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'
});
}
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);
}