Many requests with final callback, unreliable order? - javascript

I'm trying to come up with a resource loader if you will, that will load many remote resources and then execute a final callback (like rendering a DOM based on the retrieve data from these requests).
Here's the function:
var ResourceLoader = function () {
this.requests = new Array();
this.FinalCallback;
this.Add = function (request) {
this.requests.push(request);
};
this.Execute = function() {
for (var x = 0; x < this.requests.length ; x++) {
var success = this.requests[x].success;
//if this is the last of the requests...
if (x == (this.requests.length - 1) && this.FinalCallback) {
$.when($.ajax({
url: this.requests[x].url,
dataType: 'json',
error: this.requests[x].error,
method: 'GET'
}).done(success)).then(this.FinalCallback);
}
else {
$.ajax({
url: this.requests[x].url,
dataType: 'json',
error: this.requests[x].error,
method: 'GET'
}).done(success);
}
}
};
};
And here's how I use it:
var apiUrl = Utilities.Api.GetWebApiUrl();
var loader = new Utilities.ResourceLoader();
loader.Add({
url: apiUrl + 'regions/get',
success: function (results) {
Filters.Regions = results;
}
});
loader.Add({
url: apiUrl + 'currentfactors/get/83167',
success: function (results) {
Filters.NbrEmployees = results;
}
});
loader.Add({
url: apiUrl + 'currentfactors/get/83095',
success: function (results) {
Filters.Industries = results;
}
});
loader.FinalCallback = RenderBody;
loader.Execute();
function RenderBody() {
console.log('render...');
}
Obviously, I'm expecting RenderBody to be executed last. But that's not what happening. What's ironic is that I remember doing something like that before, but lost the code... Looks like I'm having a brainfart here.

As you've tagged with promise - here's a really clean solution that uses Promise.all
this.Execute = function() {
Promise.all(this.requests.map(function(request) {
return $.ajax({
url: request.url,
dataType: 'json',
error: request.error,
method: 'GET'
}).done(request.success);
})).then(this.FinalCallback);
};
or ... using JQuery when
this.Execute = function() {
$.when.apply($, this.requests.map(function(request) {
return $.ajax({
url: request.url,
dataType: 'json',
error: request.error,
method: 'GET'
}).done(request.success);
})).then(this.FinalCallback);
};

Es6 Promise has solutions for your problem, there is no need to reinvent it unless the loading of resource groups is a specific goal to abstract. Set up a Promise object for each resource request, using the constructor to assign the resolve and reject callbacks appropriately for the XHR. Keep a collection (any Iterable will do) of individualPromise.then(individualCallback) results. Your final product is obtained by Promise.all(collectionOfPromises).then(finalCallback).

Related

How to pass the result of an Asynchronous function as a deffered object

i'm trying to get my head around working with Deffered objects especially in cases where you have to perform multiple asynchronous operations on every item in an array. In the code below i just want to be able to access the result of an asynchronous array after it is complete, in my case it is the results array.
To explain the code below
ListData function derives the source data which is an array that i intend to manipulate.
getPictureComplete1 perfroms an async operation (ListDataWithPicture) on every item in the array above
The idea of step 2 was to add an image url to every item in the array from Step 1 and then use the new array as an input to step 4
could be to print the images to the page or perform additional manipulations on the array
var mydeferred = $.Deferred();
var ListData = function (){
listName = 'TeamInfo';
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('"+listName+"')/items?$select=Name/Title,Name/Name,Name/Id,Name/EMail,Name/WorkPhone&$expand=Name/Id",
type: "GET",
headers: { "ACCEPT": "application/json;odata=verbose" },
success: onQuerySucceded,
error: onQueryFailed
});
return mydeferred.promise();
}
var ListDataWithPicture = function(userId, callback) {
// execute AJAX request
$.ajax({
url: _spPageContextInfo.siteAbsoluteUrl + "/_api/web/SiteUserInfoList/items?$filter=Id eq " + userId + "&$select=Picture",
type: "GET",
async: false,
headers: { "ACCEPT": "application/json;odata=verbose" },
success: function(data){
console.log("Starting async operation for " + userId);
var pictureLink = "";
var mydata = callback(data.d.results[0].Picture.Url);
return mydata
},
error: onQueryFailed
});
}
function onQuerySucceded (data){
var PeopleCompleteList = [];
for (i=0; i< data.d.results.length; i++) {
//check if the user exists if he does store the following properties name,title,workphone,email
if(data.d.results[i]['Name'] != null){
personName = data.d.results[i]['Name'].Name.split('|')[2];
userName = data.d.results[i]['Name']['Name'];
UserTitle = data.d.results[i]['Name']['Title'];
UserphoneNumber = data.d.results[i]['Name']['WorkPhone'];
UserEmail = data.d.results[i]['Name']['EMail'];
Id = data.d.results[i]['Name']['Id'];
PeopleCompleteList.push(PersonConstructor(personName, UserTitle, UserphoneNumber,UserEmail,Id));
}
}
mydeferred.resolve(PeopleCompleteList);
}
function getPictureComplete1 (data){
var def = new $.Deferred();
var results = [];
var expecting = data.length;
data.forEach(function(entry, index) {
//this is the asynchronous function
ListDataWithPicture(entry.UserId, function(result) {
results[index] = {imageUrl: result, UserId: entry.UserId, name: entry.name, Title: entry.Title, phoneNumber: entry.phoneNumber, Email: entry.Email};
//console.log(result);
if (--expecting === 0) {
// Done!
console.log("Results:", results); //this works succeffully from here
def.resolve();
return results
//mydeferred.resolve(results);
}
});
});
return mydeferred.promise();
}
$(function () {
ListData().then(function(data){
//how can i access the results array in this function after it has completed??
var value = getPictureComplete1 (data);
//the line below results undefined,which i understand because the getPictureComplete1 function may not have completed at the time
console.log(value);
});
Because this is an asynchronous operation, the only time that you have access to the array of results from your ajax call in LineData is within the scope of the onQuerySucceded function that you assigned to the success property in your ajax configuration. The function you define here will be fired after a successful response was received.
When establishing a deferred promise, you will need to define what data resolves the promise. Whatever you pass into the resolve method will be available as a parameter within a subsequent then block on your promise chain.
I'm not seeing onQuerySucceded defined in your example, but it would look something like this:
var ListData = function (){
var mydeferred = $.Deferred();
listName = 'TeamInfo';
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('"+listName+"')/items?$select=Name/Title,Name/Name,Name/Id,Name/EMail,Name/WorkPhone&$expand=Name/Id",
type: "GET",
headers: { "ACCEPT": "application/json;odata=verbose" },
success: function onQuerySuccess(data) {
mydeferred.resolve(data);
},
error: onQueryFailed
});
return mydeferred.promise();
}
Now, after a successful call, the promise will resolve with the data. So, something like this is possible:
ListData()
.then(function (data) {
// do something with the data
})
Similarly, you will want to define what are the rejection cases for your deferred promise. For example, maybe the ajax call doesn't succeed. For this, you will want to use the reject method on the deferred object.
For example:
var ListData = function (){
var mydeferred = $.Deferred();
listName = 'TeamInfo';
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('"+listName+"')/items?$select=Name/Title,Name/Name,Name/Id,Name/EMail,Name/WorkPhone&$expand=Name/Id",
type: "GET",
headers: { "ACCEPT": "application/json;odata=verbose" },
success: function onQuerySuccess(data) {
mydeferred.resolve(data);
},
error: function onQueryFailed(error) {
mydeferred.reject(error);
}
});
return mydeferred.promise();
}
This works similarly to resolve, but will resolve to any subsequent catch or fail blocks. So, something like this will work
LineData()
.then(function (data) {
// it was successful, do something with the data
})
.catch(function (error) {
// There was an error. The then block was not called.
// Do something with the error.
})
Put even more simply, you can set mydeferred.resolve and mydeferred.reject as these properties directly. Like so:
var ListData = function (){
var mydeferred = $.Deferred();
listName = 'TeamInfo';
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('"+listName+"')/items?$select=Name/Title,Name/Name,Name/Id,Name/EMail,Name/WorkPhone&$expand=Name/Id",
type: "GET",
headers: { "ACCEPT": "application/json;odata=verbose" },
success: mydeferred.resolve,
error: mydeferred.reject
});
return mydeferred.promise();
}
You will want to do something similar with your ListDataWithPicture function so that it also returns a promise with the data that you need:
var ListDataWithPicture = function(userId, callback) {
var deferred = $.Deferred();
$.ajax({
url: _spPageContextInfo.siteAbsoluteUrl + "/_api/web/SiteUserInfoList/items?$filter=Id eq " + userId + "&$select=Picture",
type: "GET",
async: false,
headers: { "ACCEPT": "application/json;odata=verbose" },
success: function(data){
deferred.resolve(data.d.results[0].Picture.Url)
},
error: deferred.reject
});
return deferred.promise();
}
This allows you to do something like this:
LineData()
.then(function (data) {
// performing on just the first result:
return ListDataWithPicture(data[0]);
})
.then(function (url) {
// do something with the result
})
.catch(function (error) {
// do something with the error
});
Because you want to perform a aynchronous operation on each of the items in your array, I'd recommend to use Promise.all which executes and resolves an array of promises.
LineData()
.then(function (data) {
// create a map of promises
var promises = data.map(function (item) {
return ListDataWithPicture(item);
});
return Promise.all(promises);
})
.then(function (urls) {
// urls will be an array of urls resolved from calling
// ListDataWithPicture on each item in the array resolved
// from above
})
.catch(function (error) {
// do something with the error
});
Here are some good resources to learn more:
http://api.jquery.com/category/deferred-object/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Sequentially execute two functions with jQuery in for loops

I'm pretty new to Javascript. Please don't make it too harsh :)
I have two functions, both of which involve executing jQuery requests within for loops. For example,
function a(n,locations) {
for (var i = 0; i < n; i ++) {
$.ajax({
url: 'https://geocoder.cit.api.here.com/6.2/geocode.json',
type: 'GET',
dataType: 'jsonp',
jsonp: 'jsoncallback',
data: {
searchtext: input,
app_id: APP_ID,
app_code: APP_CODE,
},
success: function (data) {
handleData(data,locations);
}
});
}
The handleData() function would make changes to the empty array locations from the jQuery data. My function b(m) is of similar format but would use the updated locations as input.
Now, I have a c(n,m) in which I would like execute a() and b() sequentially:
function c(n,m) {
var locations = [];
a(n,locations);
b(m,locations);
}
From previous answers I understand that sequentially executing functions involving jQuery calls can be achieved by using promises (such as .then). However, this solution is only applicable when a(n) returns a promise, which is not achievable under the for-loop structure. Could you please share your insights on how to solve this issue? Thanks in advance for the help.
I would suggest recursion instead of your for loop. For example, you can call the function recursionExample like this,
function a(n) {
return new Promise ((resolve, reject) {
(function recursionExample(a) {
if (a === n) {
resolve;
} else {
$.ajax({ url: 'https://geocoder.cit.api.here.com/6.2/geocode.json',
type: 'GET',
dataType: 'jsonp',
jsonp: 'jsoncallback',
data: {
searchtext: input,
app_id: APP_ID,
app_code: APP_CODE,
},
success: function(data) {
handleData(data);
recursionExample(a + 1);
}
});
}
})(0);
});
}
This will then allow you to use the promise and .then functions. Like so...
function c(n,m) {
var locations = [];
a(n,locations)
.then (function() {
b(m,locations);
});
}

How to replace 'Async=false' with promise in javascript?

I have read a lot about promises but I'm still not sure how to implement it.
I wrote the folowing AJAX call with async=false in order for it to work, but I want to replace it with promise as I saw that async=false is deprecated.
self.getBalance = function (order) {
var balance;
$.ajax({
url: "/API/balance/" + order,
type: "GET",
async: false,
success: function (data) {
balance = data;
},
done: function (date) {
}
});
return balance;
}
Would you be able to help me? I just need an example to understand it.
As first point, you don't want to set an asynchronous call to false as it will lock the UI.
You could simplify your method returning the ajax object and the handle it as a promise.
self.getBalance = function (orderNumber) {
return $.ajax({
url: "/Exchange.API/accountInfo/balance/" + orderNumber,
type: "GET",
});
};
var demoNumber = 12;
self.getBalance(demoNumber).then(function(data){
console.log(data);
},function(err){
console.log("An error ocurred");
console.log(err);
});
Return promise object from getBalance method:
self.getBalance = function (orderNumber) {
return $.ajax({
url: "/Exchange.API/accountInfo/balance/" + orderNumber,
type: "GET"
});
}
and use it later like this:
service.getBalance().then(function(balance) {
// use balance here
});

PhantomJs - Getting value out of page.evaluate + ajax (Synchronous Version)

Problem: Extracting data from ajax request inside page.evaluate
Description: I usually get variables out of page.evaluate by simply returning them. However, I need to make an ajax request within the context of a page, and then I need to process its result out of the page's context.
The code I'm trying to fix is:
var theOutput = page.evaluate(function () {
return $.ajax({
async: false,
url: 'http://localhost:8080/captcha.php',
data: { filename: 'C:\\wamp\\www\\images\\0.png' },
type: 'post',
success: function (output) {
parsed_output = $.parseHTML(output);
return parsed_output[4].data.trim();
},
});
});
console.log(theOutput);
The variable parsed_output[4].data.trim() is a string. But when I log output I get a [object Object], with the properties abort, always, complete, done, error, fail, getAllResponseHeaders, getResponseHeader, overrideMimeType, pipe null, progress, promise, readyState, setRequestHeader, state, statusCode, success,then.
Question: How can I extract theOutput from page.evaluate?
Since this is a blocking AJAX request, you can create a temporary variable:
var theOutput = page.evaluate(function () {
var result;
$.ajax({
async: false,
...
success: function (output) {
parsed_output = $.parseHTML(output);
result = parsed_output[4].data.trim();
},
});
return result;
});
console.log(theOutput);
You can also directly access the responseText from the jqXHR object:
var theOutput = page.evaluate(function () {
var jqXHR = $.ajax({
async: false,
url: 'http://localhost:8080/captcha.php',
data: { filename: 'C:\\wamp\\www\\images\\0.png' },
type: 'post'
});
parsed_output = $.parseHTML(jqXHR.responseText);
return parsed_output[4].data.trim();
});
console.log(theOutput);
If you fear that async: false is deprecated, you can simply use the underlying XMLHttpRequest to use blocking execution:
var theOutput = page.evaluate(function () {
var request = new XMLHttpRequest();
request.open('POST', 'http://localhost:8080/captcha.php', false);
request.send($.param({ filename: 'C:\\wamp\\www\\images\\0.png' }));
var parsed_output = $.parseHTML(request.responseText);
return parsed_output[4].data.trim();
});
console.log(theOutput);

Execute callback function inside javascript object

I want to execute a callback function inside an object. I don't know if there is something wrong in the way I'm doing this.
I've googled for a solution, also searched on stackoverflow but couldn't find anything similar to the way I'm coding this.
PHPGateway.js
var PHPGateway = {
opt_friendlyURL: true,
opt_folder: 'ajax/',
callback_function: null,
useFriendlyURL: function (bool) {
this.opt_friendlyURL = bool;
},
setFolder: function (folder) {
this.opt_folder = folder;
},
send: function (service, method, data, callback) {
var url,
json_data = {};
if (this.opt_friendlyURL) {
url = this.opt_folder + service + '/' + method;
} else {
url = this.opt_folder + 'gateway.php?c=' + service + '&m=' + method;
}
if (data != undefined) {
json_data = JSON.stringify(data);
}
this.callback_function = (callback == undefined) ? null : callback;
$.ajax({
method: 'POST',
url: url,
data: {data: json_data},
success: this.ajax_success,
error: this.ajax_error
});
},
ajax_success: function (returned_object) {
if (this.callback_function != null) {
this.callback_function(returned_object.error, returned_object.data);
}
},
ajax_error: function () {
this.callback_function.call(false, {});
}
};
Then inside the HTML file that loads PHPGateway.js, I've the following code:
<script>
function submit_handler(event) {
event.preventDefault();
form_submit();
}
function form_callback(error, data) {
if(error == null) {
alert(data.text);
}
}
function form_submit() {
var data = {
status: $('#inStatus').val(),
amount: $('#inAmount').val(),
id: $('#inBudgetID'). val()
}
PHPGateway.send('budget', 'status', data, form_callback);
}
$('form').one('submit', submit_handler);
</script>
I get an error on this.callback_function(returned_object.error, returned_object.data);, the error is Uncaught TypeError: Object # has no method 'callback_function'.
What am I doing wrong?
Is this the best way to do it?
Thank You!
Based on minitech answer, I've updated PHPGateway.js like this. I've omitted the parts that weren't updated.
var PHPGateway = {
// Omitted code
send: function (service, method, data, callback) {
var url,
json_data = {},
that = this;
if (this.opt_friendlyURL) {
url = this.opt_folder + service + '/' + method;
} else {
url = this.opt_folder + 'gateway.php?c=' + service + '&m=' + method;
}
if (data != undefined) {
json_data = JSON.stringify(data);
}
this.callback_function = (callback == undefined) ? null : callback;
$.ajax({
method: 'POST',
url: url,
data: {data: json_data},
success: function(data, textStatus, jqXHR) {
that.ajax_success(data, textStatus, jqXHR);
},
error: function(jqXHR, textStatus, errorThrown) {
that.ajax_error(jqXHR, textStatus, errorThrown);
}
});
},
ajax_success: function (data, textStatus, jqXHR) {
if (this.callback_function != null) {
this.callback_function(true, data.data);
}
},
ajax_error: function (jqXHR, textStatus, errorThrown) {
this.callback_function.call(false, {});
}
};
Now it works!!!
In your call to $.ajax, you need to add a context option:
$.ajax({
method: 'POST',
url: url,
data: {data: json_data},
context: this,
success: this.ajax_success,
error: this.ajax_error
});
Your this variable in your Ajax success and error handlers are not pointing to the object you think they are. The context option to $.ajax() sets which object this points to in the Ajax callbacks.
Here’s your problem:
$.ajax({
method: 'POST',
url: url,
data: {data: json_data},
success: this.ajax_success,
error: this.ajax_error
});
When you set success and error to methods on this, they don’t keep their this. When a JavaScript function is called, it gets bound a this:
someFunction(); // this is undefined or the global object, depending on strict
someObject.someFunction(); // this is someObject
The built-in .call, .apply, and .bind of Function objects help you override this.
In your case, I think jQuery binds this to the Ajax object – a good reason to both not use jQuery and always use strict mode.
If you can guarantee or shim ES5 support, bind is an easy fix:
$.ajax({
method: 'POST',
url: url,
data: {data: json_data},
success: this.ajax_success.bind(this),
error: this.ajax_error.bind(this)
});
Which is equivalent to this if you can’t:
var that = this;
$.ajax({
method: 'POST',
url: url,
data: {data: json_data},
success: function() {
that.ajax_success.apply(that, arguments);
},
error: function() {
that.ajax_error.apply(that, arguments);
}
});
And now, a tip for you: don’t namespace, and if you do, don’t use this. this is great for objects that are meant to be constructed. What would seem more appropriate is something like this, if you really have to:
var PHPGateway = (function() {
var callbackFunction;
var options = {
friendlyURL: true,
…
};
…
function send(service, method, data, callback) {
…
}
…
return { send: send };
})();

Categories

Resources