AngularJS - Looping through ASYNC GET requests - javascript

I am a beginner in Javascript and AngularJS and am trying to do the below:
There is a service that takes a list as URLs as input and find their respective data in a local cache/repository custom service.
If all are found, they are returned.
If some are missing, the missing ones are fetched by making ASYNC calls to the API. They are also stored into the repository for future availability.
All received data are concatenated into a local "data" variable and sent as a single array of objects to the controller.
It is looping through each URL using $.map to fetch data and concat it to the local "data" variable. Once the loop is complete, it is returning the "data" variable.
I am aware that this is not going to work and also the reason for it. But, am not able to figure out how to achieve this using an ASYNC design pattern.
myService.factory("dataManagerService",["dataRepositoryService","dataFetchService",
function(dataRepositoryService,dataFetchService)
{
var methodObj = {};
methodObj.getObjData = function(urlList)
{
//URLs are used to fetch data from API and also to uniquely identify the fetched data.
//urlList is a list/array of URLs for which data is required.
//Get all objects from data repository which are specified in the URL list.
var data = dataRepositoryService.getData(urlList);
//If repository did not have all objects specified from the URL list, fetch the missed objects.
if (data.length < urlList.length)
{
//Get list of URL for which data was received from repository.
var excludeURLList = $.map(data,
function(obj)
{
return obj.url;
}
);
//Get list of all URLs from urlList that are missing from excludeURLList
var fetchUrlList = $.grep(urlList,
function(url)
{
return excludeURLList.indexOf(url) < 0;
}
);
//Loop through all URLs in fetchUrlList (for which data was not found in the repository)
$.map(fetchUrlList,
function(url)
{
//Make a GET request to the API
dataFetchService.fetchData(url)
.then(
function(response)
{
//Add the received response to the repository, so that its available the next time.
dataRepositoryService.setData(response.data);
//Append missing data to "data" variable
data.concat(response.data);
}
);
}
);
}
return data; /* This will be incomplete since it will be returned before all the async calls have been completed. */
}
return methodObj;
}
]);

You're going to want to use $q.all() to retrieve all of the URLS. Here's a pretty good article on this: https://www.martin-brennan.com/using-q-all-to-resolve-multiple-promises/
You're going to want to do something like this:
getObjectList(urlList) {
var promises =[]
urlList.map( function(url) { promise.push(getUrl(url) } )
$q.all(promises).then( function(responses) { assemble what you want to return here}
)
}
function getUrl(url) {
var q=$q.defer()
if (whereever you've stored the data has fetched the url already) {
return $q.when(the data)
} else {
return $http.get(url).then( function(response) {
return $q.resolve(response.data)
}, function (error) { return $q.reject(error) })
}
}

Related

set property of js module with nested callback

Im trying to set a modules variable/property from a nested function (basically a xhr callback (Api.get()) inside that module (in the init() function), but it does not work and I can not figure out why.
//posts
var Posts = (function() {
//array of all posts objects
var data = null;
//initialize the posts module
var init = function(callback) {
if(data === null) {
//load all posts
loadAll(function(response){
// data = JSON.parse(response)
var posts = JSON.parse(response)
//create array
data = posts;
// call callback
console.log(data)
callback()
})
}
}
// return all posts from api as json
var loadAll = function(callback) {
Api.get('/api/posts/get', function(response) {
callback(response)
})
}
//public interface
return {
data: data,
init: init,
loadAll: loadAll
}
})();
After calling Posts.init() I log Posts.data to the console, but it is still null. However, console.log(data) inside the init() method logs the expected array of objects im trying to assign to Posts.data. It seems that data inside the callback is another variable than Posts.data. Can someone please explain why and if possible, provide a solution for setting the modules data property inside Api.get()?
You need to have a reference to the return object so you can alter its data property after you've returned the object. One way to do this would be to create an object with the methods and data and return that object. Then you can refer to its data property internally with this.data:
// Fake API
let Api = {
get(url, cb) {
cb('["testdata"]')
}
}
//posts
var Posts = (function() {
//array of all posts objects
return {
data: null,
init(callback) {
if (this.data === null) {
//load all posts
this.loadAll((response) => { // arrow function needed here for correct `this` binding
var posts = JSON.parse(response)
//create array
this.data = posts; // add data
callback()
})
}
},
loadAll(callback) {
Api.get('/api/posts/get', function(response) {
callback(response)
})
}
}
})();
console.log("initial posts data: ", Posts.data)
Posts.init(() => console.log("After init():", Posts.data))
If you do it this way, you don't actually need the IEFE unless you plan on making multiple objects. You can just use Posts = {/* rest of the data and methods */}. This would also work well as a class instead of a plain object.

Returning array after being filled in async function - Node.js

I want to access some data from an API and ran into a problem. My code is fetching some data from the API, will compare it with a local copy of an API. I want it to store some data in an array if the local copy does not match the copy fetched from the API. The fetching and comparison is working fine. The problem comes up when I try to fill the array and want to give it back. The request function is async so my return value at the end will be undefined. My function checkForDiff should return this array after the for-loop is done, because after the foor-loop the array should be filled with the information I need. I am a newbie in Nodejs so I really do not know how to fix it. I need the array to be filled before returning it, but the async request call is causing me problems. How can I achieve this kind of behavior?
Thank you for your help in advance
function checkForDiff(){
let outdatedLanguages = [];
var options = {
url: 'https://xxxxxxxxxxxxxxxxxxxxxxxxx',
headers: {'Key': 'xxxxxxxxxxxxxxxxxxxxxx'}
};
for(let index = 0; index < locales.length; index++){
//Change url for new https request
options.url = `https://xxxxxxx?locale=${locales[index].symbol}`
//Send new https request to API
request(options, (error, response, body)=>{
var localState = hash(JSON.parse(filesystem.readFileSync(`./cards/cards-${locales[index].symbol}.json`)));
var recentState = hash(JSON.parse(body));
/If the local card base is not up to date, add locale to array
if(localState !== recentState){
outdatedLanguages.push(locales[index].symbol);
}
);
}
//Return outdatedLanguages array
return outdatedLanguages;
}
To get the correct data, you need to use promises. Instead of request callback, use promise.
Because checkForDiff() is an async function, you should return a promise from the function instead of trying to return the outdatedLanguages. For your case, you need to use Promise.all() function because you have multiple async functions. In a sense, Promise.all() waits for all tasks to accomplish. In the other part of the code where you use that function, you should be aware that the function is a promise so you should use it accordingly. Basically, you can do something like this.
function checkForDiff() {
let outdatedLanguages = [];
let promises = [];
var options = {
url: 'https://xxxxxxxxxxxxxxxxxxxxxxxxx',
headers: { 'Key': 'xxxxxxxxxxxxxxxxxxxxxx' }
};
for (let index = 0; index < locales.length; index++) {
//Change url for new https request
options.url = `https://xxxxxxx?locale=${locales[index].symbol}`
promises.push(/* request as promise */);
}
return Promise.all(promises).then(() => outdatedLanguages);
}
You call the function like this.
checkForDiff().then((outdatedLanguages) => {
// outdatedLanguages is the array you want
})
For request promise you can use request-promise package. Use the command npm install --save request-promise. Then include the package var rp = require('request-promise');. An example request is as following:
var options = {
uri: 'https://api.github.com/user/repos',
qs: {
access_token: 'xxxxx xxxxx' // -> uri + '?access_token=xxxxx%20xxxxx'
},
headers: {
'User-Agent': 'Request-Promise'
},
json: true // Automatically parses the JSON string in the response
};
rp(options)
.then(function (repos) {
console.log('User has %d repos', repos.length);
})
.catch(function (err) {
// API call failed...
});

Retrieve list items from multiple lists with urls stored in an array

How do i get list items from different lists in SharePoint using javascript. Given that all my list are stored in an array. And I need to loop through the array and execute similar functions on each list.
function initializePage() {
listcollections.forEach(function (value, index) { // listcollections is the array that contains the list url for different lists
var listtitle = value.listTitle;
var siteUrl = value.siteURL;
getItemsWithCaml(siteUrl, listtitle,
function (camlItems) {
var listItemEnumerator = camlItems.getEnumerator();
while (listItemEnumerator.moveNext()) {
var EventsItem = new Events();
var listItem = listItemEnumerator.get_current();
EventsItem.eventTitle = listItem.get_item('Title');
EventsItem.eventDate = listItem.get_item('EventDate');
EventsItem.eventId = listItem.get_id();
EventsItem.eventSite = siteUrl;
EventsItem.eventList = listtitle;
EventsCollection.push(EventsItem);
}
},
function (sender, args) {
alert('An error occurred while retrieving list items:' + args.get_message());
});
})
};
function getItemsWithCaml(siteUrl, listtitle, header, success, error)
{
var hostWebContext = new SP.ClientContext(siteUrl);
var list = hostWebContext.get_web().get_lists().getByTitle(listtitle);
var caml = new SP.CamlQuery();
//Create the CAML that will return only items expiring today or later
caml.set_viewXml("<View><Query><Where><Geq><FieldRef Name=\'Expires\'/><Value Type=\'DateTime\'><Today /></Value></Geq></Where> </Query></View>");
var camlItems = list.getItems(caml);
hostWebContext.load(camlItems);
hostWebContext.executeQueryAsync(
function () {
success(camlItems);
},
error
);
};
//need to execute certain functions to format each list item
// I am not able to retrieve all list items in a single variable to be able to display data from all lists together
In the example below, I create a JavaScript object named ListDataCollection that contain a property Lists that is an array of objects.
Each of those contain a Url property whit the url of the lists I want to get the content.
Then I loop trough the array of objects and call the Sharepoint REST api for each Url.
Each time a call is complete :
I create a Data property on the current object with the data received
by the ajax call to the REST api.
I also update the current object Completed property to true.
Then I call a function named ifAllCompleted that check if all ajax calls are ended.
When all data is received, I log the word Completed on the browser console.
At this point, you have an array of objects. Each object contains the data of one Sharepoint list.
For example :
ListDataCollection.Lists[0].Data.d.results[0].Title
will contain the value of the Title Column of the first element in the first list.
If you want, you can merge all data in one array using the concat function in JavaScript.
Look at the 4 lines of code following the word Completed.
Hope this can help!
<script>
var ListDataCollection = {
Lists:[{
Url:"http://Url.Of.Your.Site.Collection/_api/web/lists/getbytitle('List1')/Items",
Completed:false
},{
Url:"http://Url.Of.Your.Site.Collection/_api/web/lists/getbytitle('List2')/Items",
Completed:false
},{
Url:"http://Url.Of.Your.Site.Collection/_api/web/lists/getbytitle('List3')/Items",
Completed:false
}]
};
function ifAllCompleted() {
for (var i=0;i<ListDataCollection.Lists.length;i++) {
if (!ListDataCollection.Lists[i].Completed) {
return false;
}
}
console.log('Completed');
var arrayOfAllData = ListDataCollection.Lists[0].Data.d.results;
arrayOfAllData = arrayOfAllData.concat(ListDataCollection.Lists[1].Data.d.results);
arrayOfAllData = arrayOfAllData.concat(ListDataCollection.Lists[2].Data.d.results);
console.log('Total elements : ' + arrayOfAllData.length);
}
$(document).ready(function(){
for (var x=0;x<ListDataCollection.Lists.length;x++) {
$.ajax({
url:ListDataCollection.Lists[x].Url,
indexValue:x,
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: function (data, status, xhr) {
ListDataCollection.Lists[this.indexValue].Data = data;
ListDataCollection.Lists[this.indexValue].Completed = true;
ifAllCompleted();
},
error: function (xhr, status, error) {
console.log('error');
}
});
}
});
</script>

How to implement asynchronous computed observable with multiple $.ajax calls?

I'm attempting to implement an asynchronous computed observable as show here.
I can do it successfully for one ajax call. The challenge I have at the moment is how to perform various ajax calls in a loop building an array asynchronously and then returning the array to my computed observable array using jQuery promises.
Basically the HTML form works in the following way:
This a student course form.
For each row, users type the person number and on another column they'll type a list of course ids separated by commas. Eg 100, 200, 300.
The purpose of the computed observable is to store an array
containing course details for the courses entered in step 2.
The details are obtained by firing ajax calls for each course and storing HTTP response in the array.
I don't want users to wait for the result, thus the reason to implement an async computed observable.
My problem: I'm having problem returning the value of the final array to the observable. It's always undefined. The ajax calls work fine but perhaps I'm still not handling the promises correctly.
Here's the code for my class:
function asyncComputed(evaluator, owner) {
var result = ko.observable(), currentDeferred;
result.inProgress = ko.observable(false); // Track whether we're waiting for a result
ko.computed(function () {
// Abort any in-flight evaluation to ensure we only notify with the latest value
if (currentDeferred) { currentDeferred.reject(); }
var evaluatorResult = evaluator.call(owner);
// Cope with both asynchronous and synchronous values
if (evaluatorResult && (typeof evaluatorResult.done == "function")) { // Async
result.inProgress(true);
currentDeferred = $.Deferred().done(function (data) {
result.inProgress(false);
result(data);
});
evaluatorResult.done(currentDeferred.resolve);
} else // Sync
result(evaluatorResult);
});
return result;
}
function personDetails(id, personNumber, courseIds) {
var self = this;
self.id = ko.observable(id);
self.personNumber = ko.observable(personNumber);
self.courseIds = ko.observable(courseIds);
// Computed property to extract PIC details for additional PICs.
// This is computed observable which returns response asynchronously
self.courseDetails = asyncComputed(function () {
var courseIdsArray = self.courseIds().split(",");
var arr = [];
var arr_promises = [];
function getCourseDetails(courseId) {
var dfrd = $.Deferred();
var content = {};
content.searchString = courseId;
var url = 'MyURL';
return $.ajax(url, {
type: 'POST',
dataType: 'json',
data: requestData, // content of requestData is irrelevant. The ajax call works fine.
processdata: true,
cache: false,
async: true,
contentType: "application/json"
}).done(function (data) {
arr.push(new PicDetails(data.GenericIdentifierSearchResult[0]));
}).fail(function () {
alert("Could not retrieve PIC details");
}).then(function () {
dfrd.resolve();
});
}
if (courseIdsArray.length > 0) {
$.each(courseIdsArray, function (index, courseId) {
if (courseId.length > 0) {
arr_promises.push(getCourseDetails(courseId));
}
});
};
$.when.apply($, arr_promises).done(function () {
return arr;
})
}, this);
}
I think you dont really need a separate api/code for this.
You could just create observables for every input/value that changes on your site, and create a computed observable based on those.
e.g in rough pseudo code
self.id = ko.observable(id);
self.personNumber = ko.observable(personNumber);
self.courseIds = ko.observable(courseIds);
self.courseDetailsArray = ko.observableArray([]);
self.courseDetails = ko.computed(function() {
//computed the course details based on other observables
//whenever user types in more course ids, start loading them
$.get( yoururl, {self.courseIds and self.id}).success(data) {
when finished async loading, parse the data and push the new course details into final array
self.courseDetailsArray.push( your loaded and parsed data );
//since courseDetailsArray is observableArray, you can have further computed observables using and re-formatting it.
}
});
I have something a bit different from your approach, but you can build something like an asyncComputed out of it if you prefer:
make a simple observable that will hold the result
make a dictionary of promises that you'll basically keep in sync with the array of course ids
when the array of course ids change, add / remove from the dictionary of promises
wrap all your promises in a when (like you're doing) and set the result when they're all done
Basic idea:
var results = ko.observable([]);
var loadingPromises = {};
var watcher = ko.computed(function () {
var ids = ko.unwrap(listOfIds);
if (ids && ids.length) {
ids.forEach(function (id) {
if (!loadingPromises.hasOwnProperty(id)) {
loadingPromises[id] = $.get(url, {...id...});
}
});
var stillApplicablePromises = {};
var promises = []; // we could delete from loadingPromises but v8 optimizes delete poorly
Object.getOwnPropertyNames(loadingPromises).forEach(function (id) {
if (ids.indexOf(id) >= 0) {
stillApplicablePromises[id] = loadingPromises[id];
promises.push(loadingPromises[id]);
}
});
loadingPromises = stillApplicablePromises;
$.when.apply(this, promises).then(function () {
// process arguments here however you like, they're the responses to your promises
results(arguments);
});
} else {
loadingPromises = {};
results([]);
}
}, this);
This is the file (that may change) where you can see this "in real life": https://github.com/wikimedia/analytics-dashiki/blob/master/src/components/wikimetrics-visualizer/wikimetrics-visualizer.js
And the basic fiddle: http://jsfiddle.net/xtsekb20/1/

Fetch data on different server with backbone.js

I can't see what the problem with this is.
I'm trying to fetch data on a different server, the url within the collection is correct but returns a 404 error. When trying to fetch the data the error function is triggered and no data is returned. The php script that returns the data works and gives me the output as expected. Can anyone see what's wrong with my code?
Thanks in advance :)
// function within view to fetch data
fetchData: function()
{
console.log('fetchData')
// Assign scope.
var $this = this;
// Set the colletion.
this.collection = new BookmarkCollection();
console.log(this.collection)
// Call server to get data.
this.collection.fetch(
{
cache: false,
success: function(collection, response)
{
console.log(collection)
// If there are no errors.
if (!collection.errors)
{
// Set JSON of collection to global variable.
app.userBookmarks = collection.toJSON();
// $this.loaded=true;
// Call function to render view.
$this.render();
}
// END if.
},
error: function(collection, response)
{
console.log('fetchData error')
console.log(collection)
console.log(response)
}
});
},
// end of function
Model and collection:
BookmarkModel = Backbone.Model.extend(
{
idAttribute: 'lineNavRef'
});
BookmarkCollection = Backbone.Collection.extend(
{
model: BookmarkModel,
//urlRoot: 'data/getBookmarks.php',
urlRoot: 'http://' + app.Domain + ':' + app.serverPort + '/data/getBookmarks.php?fromCrm=true',
url: function()
{
console.log(this.urlRoot)
return this.urlRoot;
},
parse: function (data, xhr)
{
console.log(data)
// Default error status.
this.errors = false;
if (data.responseCode < 1 || data.errorCode < 1)
{
this.errors = true;
}
return data;
}
});
You can make the requests using JSONP (read about here: http://en.wikipedia.org/wiki/JSONP).
To achive it using Backbone, simply do this:
var collection = new MyCollection();
collection.fetch({ dataType: 'jsonp' });
You backend must ready to do this. The server will receive a callback name generated by jQuery, passed on the query string. So the server must respond:
name_of_callback_fuction_generated({ YOUR DATA HERE });
Hope I've helped.
This is a cross domain request - no can do. Will need to use a local script and use curl to access the one on the other domain.

Categories