I am starting to re-factor some code to use promises and caching to ensure cleaner code. My Code (See below) has tried to use this new premise and is working with slight success. The main issue is that the caching mechanism that is uses by default prevents my from being able to "pass" in a dynamic url value without it returning the same cached results every time. How can i update the code below to use a cache key?
"use strict";
var FLEX = window.FLEX|| {};
FLEX.Following = FLEX.Following|| {};
FLEX.Following.Process = function () {
var deferred = $.Deferred(),
execute = function (followUrl) {
$.ajax(
{
url: _spPageContextInfo.webAbsoluteUrl +
followUrl,
method: "GET",
headers: {
"accept": "application/json;odata=verbose",
},
success: function (data) {
deferred.resolve(data);
},
error: function (err) {
deferred.reject(err);
}
}
);
return deferred;
};
return {
execute: execute
}
}();
FLEX.Following.Init = function (divName, followUrl) {
FLEX.Following.Process.execute(followUrl).promise().then(
//success
function (data) {
var html = "";
$.each(data.d.Followed.results,function(index, value){
html += value.Name + "<br />";
});
$(divName).html(html);
},
//failure
function(err) {
$(divName).html('Failed');
}
);
}
I then call the code using the following lines:
$(document).ready(function() {
FLEX.Following.Init("#followed-sites", "/_api/social.following/my/followed(types=4)");
FLEX.Following.Init("#followed-people", "/_api/social.following/my/followed(types=1)");
FLEX.Following.Init("#followed-documents", "/_api/social.following/my/followed(types=2)");
});
The resulting output "works" however i get the same results in each div as it appears that every subsequent call jQuery gets the cached version from one of them and therefore each result set is identical. How can i ensure that i can use the same functions with the different dynamic urls as above and cache them separately.
Thanks
I think the root problem is that you create a single $.Deferred() and then you try to reuse it for multiple calls to execute() and that won't ever work. A deferred is a one-shot trigger. Once it has been fulfilled or rejected it stays that way and contains the same data forever. If you want a different result on subsequent calls, you have to create a new deferred for each call. So, because you were creating your deferred outside of the execute() function, you only ever had one deferred and thus the first result would just stick forever.
The fix here is to use a separate promise/deferred for each call to execute(). Since $.ajax() already returns a new promise each time you call it - you can use that instead of creating your own $.Deferred manually which is an anti-pattern. Let's refactor and solve both issues at once.
Here's your function refactored so solve those issues:
var execute = function (followUrl) {
return $.ajax({
url: _spPageContextInfo.webAbsoluteUrl + followUrl,
method: "GET",
headers: {"accept": "application/json;odata=verbose"}
});
};
Then, in this line of code:
FLEX.Following.Process.execute(followUrl).promise().then(
You should remove the .promise() because my change above is already returning a promise so this line can just become this:
FLEX.Following.Process.execute(followUrl).then(
Related
What I've been trying is to call $.ajax multiple times, until the result is empty.
Here is a simplified version of my experimentation...
var objects = [];
function getObjects (page){
return new Promise(function(resolve, reject){
$.ajax({
url : url+"&pageNumber="+page,
type:'get',
dataType:'json',
async : true,
}).done(function(results){
if(results.length > 0){
objects = objects.concat(results);
return getObjects(page + 1);
}
console.log("Finished",objects);
resolve(objects);
}).fail(reject);
});
};
var page = 0;
getObjects(page).then(function(objects) {
console.log("getObjects completed",objects);
}).catch(function(error){
console.log(error);
});
Put simply, getObjects(page) will be called repeatedly until all the objects are retrieved.
The first console.log() shows what I expect, but the then function at the end doesn't run.
Interestingly, when I intentionally make the function fail, the catch part works properly. Clearly, resolve isn't working, but I don't see why.
Any advice will be appreciated.
EDIT
I've tried reject and reject(objects), neither worked.
jQuery Ajax requests are promises. They may not be Promise instances, but they implement the Promise interface. There is no need to wrap them.
function getObjects(page, objects) {
page = page || 0;
objects = objects || [];
return $.get(url, {pageNumber: page}).then(function (results) {
objects.push.apply(objects, results);
return results.length ? getObjects(page + 1, objects) : objects;
});
}
getObjects().done(function (objects) {
console.log("getObjects completed",objects);
}).fail(function(error){
console.log(error);
});
Also, in this case there is no reason to use the more wordy $.ajax(). $.get() will do just fine. jQuery will automatically detect a JSON reponse, and Ajax requests are async anyway, so setting the dataType and async parameters is redundant.
I'm trying to call json object in another JS file but there seems to be a timing issue. So I put a setTimeout below but the setTiemout runs twice, first with the object populated, then again with the object undefined and then undefined the passed to the second JS file. I also tried clearTimeout but then it didn't run at all. Then I tried a boolean but it still ran twice. I think the issue might be cause of the deferred, is there any way around this?
var json = {};
$('.submit').on('click', function (e) {
e.preventDefault();
var input = $('.url-input');
var def = $.Deferred();
$.ajax({
type: "GET",
url: $(input).val(),
data: input.serialize(), // serializes the form's elements.
success: function(data) {
var category;
$(data).find('a[href*="categories"]').filter(function(){
var data = $(this);
category = data.text().trim();
json.category = category;
});
def.resolve();
return def.promise;
}
}).then(function () {
$('.cust-viz.viz-2').html('<iframe class="bubble_chart" src="bubble_chart.html" height="500"></iframe>');
if (json.category) {
hideShowViz('show');
}
});
});
}
setTimeout(function () {
json = json;
}, 5000);
based on answers, I did
var json ={};
$('.submit').on('click', function (e) {
e.preventDefault();
var input = $('.url-input');
$.ajax({
type: "GET",
url: $(input).val(),
data: input.serialize(), // serializes the form's elements.
}).then(function (data) {
var category;
$(data).find('a[href*="categories"]').filter(function(){
var data = $(this);
category = data.text().trim();
json.category = category;
});
if (json.category) {
$('.cust-viz.viz-2').html('<iframe class="bubble_chart" src="bubble_chart.html" height="500"></iframe>');
hideShowViz('show');
}
});
});
}
Still returning empty.
There are multiple issues with the code.
Regarding timing, you are likely to have issues with this pattern ...
$.ajax({
...
success: fnA
})
.then(fnB);
... especially if fnB is dependent on something done by fnA.
In practice, it's never good to mix a success handler written as an $.ajax() option with one written as a .then() callback.
So refactor as follows :
$.ajax({
...
})
.then(fnA)
.then(fnB);
Now,
if fnA is asynchronous, it must return a promise to inhibit progress to fnB until that promise resolves.
if fnA is wholly synchronous, as appears to be the case, you can return a value or , more typically, you would merge fnA with fnB.
Note: A success handler written as an $.ajax() option does not possess the same power, and that's the reason why we don't mix success: fn with .then().
$.ajax({
...
})
.then(fnAB);
With those rules in mind, you should be able to better debug the other issues.
I need to populate an HTML table using records retrieved via a series of AJAX
requests. The AJAX requests are all generated in a while loop. The total number of requests made is governed by a value provided by the server in a previous exchange of information. The AJAX requests need to be handled asynchronously while the results of the request need to be processed in the order in which originally requested.
The problem, of course is that the responses to the requests do not always
get answered in order. So, after reading up on jquery's Deferred/promise interface I thought I had a solution in hand. But for the life of me can't get my head around how to defer the processing (populating the table) of the results of a requests until the results of previous request(s) have been hanlded. I've found many examples on StackOverflow that get me close, but I can't seem to connect the dots to get it all working in my application.
First I tried using an array of Deferred objects, thinking that I could use indexed references to them to make the processing of one set of records dependent upon the completion of the processing of the previous set (in "as requested" order) set of data. But I couldn't figure out how to create a promise/Deferred object associated with the actual processing of the data - using .then().
var deferreds = [];
var recsPerPkt = 20;
var recRqstd = 0;
while(recsRqstd < totalRecsAvailable) {
console.log("Next record index to request: " + nextNdxRqst)
// Collect an array of the "promise" objects that $.ajax() returns for each call.
deferreds.push( $.ajax({
url: 'eventSummaryData',
type: 'get',
cache: false,
data: {StartNdxNum: nextNdxRqst, NumRecords: recsPerPkt}
}) // End $.ajax({ url: 'trainSummaryData', ...
); // End deferreds.push()
recsRqstd += recsPerPkt;
nextNdxRqst = recsRqstd;
if (deferreds.length > 1)
{
deferreds[deferreds.length - 2].then(
function (jsonData) {
if (jsonData.ok) {
// Now display the rows/records included in this packet.
displayrRecordsInTable({"rows": jsonData.rows});
}
},
function(){
$('#error-msg').text('HTTP error: ' + errorThrown);
}
);
}
}
$.when.apply(null, deferreds).then( function(){
console.log("Processing of AJAX'd data complete. ")
configureTableControls();
});
Then I found a "pattern" that chained the processing functionality using then(),
but the example didn't quite fit my exact situation and ended up having no reference to the http response data in my process-the-data handler. When I run this, the browser logs to the console, "jsonData undefined" .
var prevPromise = $.Deferred().resolve();
var recsPerPkt = 20;
var recRqstd = 0;
while(recsRqstd < totalRecsAvailable) {
prevPromise = prevPromise.then(function(){
return $.ajax({
url: 'eventSummaryData',
type: 'get',
cache: false,
data: {StartNdxNum: nextNdxRqst, NumRecords: recsPerPkt}
});
}).then(function (jsonData) {
if (jsonData.ok) {
// Now display the rows/records included in this packet.
displayTrainSummaryRows({"rows": jsonData.rows});
}
},
function(){
$('#error-msg').text('HTTP error: ' + errorThrown);
}
);
recsRqstd += recsPerPkt;
nextNdxRqst = recsRqstd;
}
So, how can I enforce the processing of AJAX requested data in the sequence in
which the requests were originally made? Thanks in advance.
I would suggest using native Promises over jQuery's deferred objects. They are much more readable, easier to understand, native to the language, and have increasing browser support (with the exception of all IE versions). To account for browser support, use a polyfill like es6-promise and you'll be set. This article does a good job of explaining the basics of promises.
Print results one by one, slower overall
Check out the section of that article I linked to, titled "Parallelism and sequencing" because that really goes in depth on how this works. Essentially you need to create a promise generator function, some sort of mapping for how many ajax requests you need to make (in this case just an array that goes up by 20 each time), and a sequence variable that holds the previous promise that was looped through.
var totalRecsAvailable = 10; //generated elsewhere apparently
var recsPerPkt = 20;
var nextNdxRqst = 0;
var recordRanges = [];
var sequence = Promise.resolve(); //initialize to empty resolved promise
//this generates a promise
function getMyData(startPosition) {
return new Promise(function(resolve,reject){
$.ajax({
type: 'GET',
dataType: 'json',
url: 'eventSummaryData',
data: {StartNdxNum: startPosition, NumRecords: recsPerPkt}
success: function(response){resolve(response);},
error: function(response){reject(response);}
});
});
}
//build out array to inform our promises what records to pull & in which order
for (var i = 0; i < totalRecsAvailable; i++) {
recordRanges.push(nextNdxRqst);
nextNdxRqst += recsPerPkt;
}
//loop through record ranges, chain promises for each one
recordRanges.forEach(function(range) {
sequence = sequence.then(function() {
return getMyData(range); // return a new Promise
}).then(function(data) {
//do stuff with the data
addToHtmlTable(data.something);
}).catch(function(error) {
//something went wrong
console.log(error);
});
});
As outlined in that article, using reduce instead of a forEach is actually a bit better, but I thought this was more clear what was happening.
Wait until all processes resolve, faster overall
For slightly faster performance, you should use Promise.all(). This takes an iterable (like an array) of promises, runs those promises asynchronously, and then saves the results to an array in the order they were passed. If one of the promises fails, the whole thing will fail and give an error. This sounds exactly like what you need. For example, you could do something like this:
var recsPerPkt = 20;
var nextNdxRqst = 0;
var totalRecsAvailable = 10; //generated elsewhere apparently
var promises = [];
//this generates a promise
function getMyData(startPosition, recordsNumber) {
return new Promise(function(resolve,reject){
$.ajax({
type: 'GET',
dataType: 'json',
url: 'eventSummaryData',
data: {StartNdxNum: startPosition, NumRecords: recordsNumber}
success: function(response){resolve(response);},
error: function(response){reject(response);}
});
});
}
//create list of promises
for (var i = 0; i < totalRecsAvailable; i++) {
promises.push(getMyData(nextNdxRqst,recsPerPkt));
nextNdxRqst += recsPerPkt;
}
//This will run once all async operations have successfully finished
Promise.all(promises).then(
function(data){
//everything successful, handle data here
//data is array of results IN ORDER they were passed
buildTable(data);
},
function(data){
//something failed, handle error here
logoutError(data);
}
);
That should set you down the right path.
I don't know of any jQuery function that does what you want, but here is a function processInOrder that will not perform your callback until all previous async ops have resolved while still allowing you to access their results
function processInOrder(arr, cb){
if( arr.length > 0 ){
arr[0].then(function(result){
cb(result);
processInOrder(arr.slice(1), cb);
});
}
}
var deferreds = [];
for(var i=0; i<4; i++){
deferreds.push( asyncRequest(i) );
}
processInOrder(deferreds, display);
Note that while I'm not positive, I'm fairly sure that this form of recursion does not nuke the call stack for large numbers of requests.
Here it is in a jsFiddle
I'm a bit confused I am using the result of callone() to modify a global object(I'm not sure of a better way to do this) trying to accomplish this with deferred. By the time time I calltwo() the global object should be modified with the new data
var obj = {};
var id = obj.id;
//global object
$.when(callone(obj)).then(calltwo(id),function(data)
{
});
- Ajax function :1
function callone(requiredData)
{
var d = new $.Deferred();
var ajaxCall1 = $.ajax({
type:"POST",
url: 'AB/',
data: requiredData,
success: function(data) {
//return data to the callee?
d.resolve(p_obj);
//set ID on the object
obj.id = data.id;
return obj;
},
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ': ' + errorThrown);
},
always: function(data) { }
});
}
function calltwo(id from callback one)
{
}
I've included a much simpler implementation below.
callone() must return a deferred or promise for you to wait on it or chain other operations to it and you can just use the promise that $.ajax() already returns rather than creating your own.
Further, there is no reason to use $.when() here because it really only adds value when you're trying to wait on multiple promises running in parallel which isn't your case at all. So, you can just use the .then() handler on the individual promises you already have.
In addition, you really don't want to use globals when processing async operations. You can chain the promises and pass the data right through the promises.
Here's what callone() should look like:
function callone(requiredData) {
return $.ajax({
type: "POST",
url: 'AB/',
data: requiredData
});
}
function calltwo(...) {
// similar to callone
// returns promise from $.ajax()
}
callone(...).then(function(data) {
// when callone is done, use the id from its result
// and pass that to calltwo
return calltwo(data.id);
}).then(function(data) {
// process result from calltwo here
}, function(err) {
// ajax error here
});
Notice that this code isn't creating any new Deferred objects. It's just using the promise that is already returned from $.ajax(). Note also that it isn't use success: or error: handlers either because those also come through the promises.
Also, note that return a promise from within a .then() handler automatically chains it into the previous promise so the previous promise won't be resolved until the newly returned promise is also resolved. This allows you to keep the chain going.
Also, note that returning data from an async callback function does not return data back to the caller of the original function so your attempt to return something from the success: handler was not accomplishing anything. Instead, use promises and return data through the promises as they are specifically designed to get async data back to the .then() handlers.
I am trying to wait for an ajax call to finish saving a model before saving the next model in the list. I was googling around and saw some stuff about deferred objects which are new to me, and another answer that had a recursive function do it. I tried the recursive method because it seemed to make a little more sense than with deferred objects and using $.when.apply($, arrayOfAjaxCalls).then(). So that code (the recursive one, looks like:
saveModel(index, numRequests) {
var self = this;
if (index < numRequests) {
var sample = self.samplesToSave[index];
return $.ajax({
url: model.url,
contentType: "application/json",
type: "POST",
data: JSON.stringify(model),
crossDomain: $.support.cors,
xhrFields: {
withCredentials: $.support.cors,
},
success: function(data) {
console.log("JUST SAVED");
console.log(data);
},
error: function(xhr: any) {
console.log(xhr);
},
}).then(() => {
self.saveModel(index + 1, numRequests);
});
}
}
I call this like:
saveModel(0, _.size(myCollection)
It doesn't actually wait for the ajax call to finish in its current state before calling the next saveModel. It basically just synchronously calls saveModel for each item in the collection in order. Any thoughts on what I'm missing? If there's a better solution with $.Deferred, I'm ok with that as well. Thanks.
Edit: Sorry it meant to say saveModel in the last line of the saveModel function. Was trying to get rid of parts that were domain specific. And I'm using typescript, not coffeescript
New attempt:
saveSampleNew() {
var d = $.Deferred();
d.resolve();
var p = d.promise();
var self = this;
self.samplesToSave.forEach(sample => p = p.then(() => self.makeSaveRequest(sample)));
return p;
}
makeSaveRequest(sample) {
var self = this;
return $.ajax({
url: "samples",
contentType: "application/json",
type: "POST",
data: JSON.stringify(sample),
crossDomain: $.support.cors,
xhrFields: {
withCredentials: $.support.cors,
},
success: function(data) {
console.log("SAVED12");
console.log(data);
},
});
}
Because this code depends on other async calls from completing, I call this new attempt like this:
this.saveContainers(project).then(() => {
}).done(() => {
self.saveSampleNew();
});
No, it should work this way. If you think it doesn't wait, please provide more information on how you call it and how experience that it does synchronously recurse.
There is one catch however with the recursive call:
.then(function() {
self.saveModel(index + 1, numRequests);
})
The promise that is returned by then, and subsequently by your saveModel method, does resolve directly with the first ajax call, it does not wait for the recursive chain. The other ajax calls are still happening (sequentially, as expected), but are not being tracked by the resulting promise.
To get that, and properly chain the promises so that it resolves with the result of the last ("innermost") promise, you will need to return the promise from the callback to then:
.then(function() {
return self.saveModel(index + 1, numRequests);
})
I'd probably use a for loop rather than a recursive call here, generally - I find those easier to read in this context.
saveModel() {
var d = $.Deferred(); d.resolve();
var p = d.promise(); // to start the chain
this.samplesToSave.forEach(sample => p = p.then(() => makeSaveRequest(sample));
return p;
}
makeSaveRequest(sample) {
return $.ajax({...}); // make request using `sample` as data
}