Angular foreach wants to wait until response for single call - javascript

I have thousands of records in my array. I wish to perform some function on each element of my array. Though my functions works fine, but I want to delay the call of next item until first is parsed properly. I don't have relevancy of return either it is true or not. I just wish to delay loop until single item is parsed.
I tried.
$scope.getInfo = function (funcName) {
$http.get(Youtube.getDetails(funcName)).success(function (data, status, headers, config) { // this callback will be called asynchronously when the response is available
if (!angular.isUndefinedOrNull(data.nextPageToken)) {
$scope.form.playlistToken = data.nextPageToken;
} else {
$scope.form.playlistToken = "";
$scope.form.playlistUrl = "";
}
if (data.items.length > 0) {
angular.forEach(data.items, function (value, key) {
var youTubeData = Youtube.parseVideoDetail(value);
if (!angular.isUndefinedOrNull(youTubeData.videoId)) {
Video.find({filter: {where: {videoId: youTubeData.videoId}}}).$promise.then(function (data) {
if (data.length == 0) {
Video.create(youTubeData).$promise.then(function (value, responseHeader) {
$scope.items.splice(0, 0, youTubeData);
youTubeData = {};
// $scope.form.url = "";
toastr.success("Record saved successfully", "Success");
}, function (reason) {
toastr.error("Video can't be saved. Please check.", "Error");
});
} else {
duplicate.push(youTubeData.videoId);
toastr.error("Video already exist with id - " + youTubeData.videoId, "Error");
}
});
}
});
} else {
failed.push(funcName.values.id);
toastr.warning("No details found !!", "Warning");
}
}).error(function (data, status, headers, config) { // called asynchronously if an error occurs or server returns response with an error status.
toastr.error("Youtube API failed to fetch data.", "Error");
});
}
$scope.saveVideo = function () {
// var videoId = Youtube.getYoutubeParser($scope.form.url);
var arrVid = []; // this is actually an array of 2K+ items.
angular.forEach(arrVid, function (value, key) {
var videoId = value.replace("?v=", "");
Youtube.params.videoInfo.values.id = videoId;
$scope.getInfo(Youtube.params.videoInfo);
});
console.log("Failed Videos" + JSON.stringify(failed));
console.log("Duplicate Videos" + JSON.stringify(duplicate));
}

don't use foreach for your scenario. you can utilize $timeout or $interval of angularjs. both of them perform start and stop operations with some interval.

Related

Passing a variable between functions using jQuery

I am having trouble passing a variable from one function to another.
This code is from a PhoneGap app that I am working on, the idea is a QR code gets scanned using the offlineScan function which calls checkforOfflineTicket to check the local storage for ticket validation and returns the variable ticketCheck to determine whether the ticket is accepted.
My code below:
function checkforOfflineTicket(ticketID){
var ticketCheck = '1';
db = openDatabase(shortName, version, displayName,maxSize);
db.transaction(function(transaction) {
transaction.executeSql('SELECT * FROM tickets where ticketid=(?)', [ticketID],
function(transaction, result) {
if (result.rows.length == '1') {
if(result.rows.item(0)['status'] == '0'){
ticketCheck = 'OK';
}
else if(result.rows.item(0)['status'] == '1'){
ticketCheck = 'DUPLICATE';
}
else{
ticketCheck = 'ERROR';
}
}
else{
ticketCheck = 'NONE';
}
alert('the ticket check is '+ticketCheck);
},function(transaction, error) {
console.log("Error processing SQL: "+error.message);
});
},errorHandler,nullHandler);
return ticketCheck;
};
function offlineScan(){
cordova.plugins.barcodeScanner.scan(
function (result) {
if(!result.cancelled){
if(result.format == "QR_CODE"){
var ticketCheck = 'test';
var ticketID = result.text; // The ticketID is the full url
values=ticketID.split('='); // Split it at = to get the tickethash
one=values[0]; // url
two=values[1]; // hash
ticketCheck = checkforOfflineTicket(two);
alert('ticket check should be '+ ticketCheck);
} // End if result is QR
}
},
function (error) {
alert("Scanning failed: " + error);
}
);
}
The checkforOfflineTicket function is currently returning the alert the ticket check is OK and then second alert in the offlineScan function returns ticket check should be undefined. I have tried returning the variable in different places but no matter where I put it it does not get passed to the offlineScan function.
What am I missing? Thank you for any help!
This is caused by asynchronous method calls in your code. In checkforOfflineTicket the function to fetch the result set and doing the alert is called asynchronously to your offlineScan function. You have to chain your functions to get the correct order of execution. Following shows one possible way of chaining:
function checkforOfflineTicket(ticketID, callback) {
var ticketCheck = '1';
db = openDatabase(shortName, version, displayName,maxSize);
db.transaction(function(transaction) {
transaction.executeSql('SELECT * FROM tickets where ticketid=(?)', [ticketID],
function(transaction, result) {
...
alert('the ticket check is '+ticketCheck);
if (callback) callback();
}, ...
}
}
function offlineScan(){
cordova.plugins.barcodeScanner.scan(
function (result) {
if(!result.cancelled){
if(result.format == "QR_CODE"){
...
ticketCheck = checkforOfflineTicket(two, function() {
alert('ticket check should be '+ ticketCheck);
);
} // End if result is QR
}
},
function (error) {
alert("Scanning failed: " + error);
}
);
}

Problems with an angular foreach waiting for http calls

I'm building an ionic project where users can play a tour (which the data is from an API)
Every tour has an amount of parts that users can play at a certain point on the map. This app must be able to be a 100% offline app, so when the user enters the code of a tour, the data must be retrieved from the API before the user can proceed (so the app will put all the data of the tour offline). Every part has an image, video, audio which is getting downloaded at start of the app.
The problem is that the function call, who is downloading all the data, is not synchronous. The console.log's are saying that the function already ends before all data is downloaded. Pieces of code below:
function getAndFillFullTour() {
vm.showLoader = true;
// load data
TourFactory.getFullTour(vm.tourData.number, function(data){
if(data.state == 'success'){
vm.tourData = data;
var test = downloadData(function(){
// hide loader and continue tour
});
} else {
console.log('error');
}
});
}
This function calls the factory who is getting the full tour including paths of images of each part which is needed to get downloaded on the users device. The downloadData function is the following function:
function downloadData(callback) {
angular.forEach(vm.tourData.parts, function(value, key){
var part = value;
var i = key;
if(part.image !== "") {
TourFactory.getPartImage(part, tourId, function(data){
vm.tourData.parts[i].partImage = data;
console.log('executed with picture ' + i);
});
}
});
if(callback)
callback();
}
Unfortunately, the forloop itself is performing synchronous but it is not waiting for the factory call to complete. I tried a lot of alternatives with promises, but without luck. Could anyone help? I need to wait for the http call to be finished in order to get a response from the downloadData call.
the getPartImage() is just an example, there are five functions like this each for loop which need to be completed first before I get a response in the downloadData call.
Take a look at $q.all or here- it is a promise helper function that can wait for multiple promises to complete. It's result is a promise as well, so you can chain it with other promises.
// Promise function that knows how to download a single part
function downloadPart(myurl) {
// return http promise
return $http({
method: 'GET',
url: myurl
});
};
// Aggregat epromise that downloads all parts
function downloadAllParts(parts) {
var defer = $q.defer(); // Setup return promise
var partsPromises = []; // Setup array for indivudual part promises
angular.forEach(parts, function(part) { // Iterate through each part
// Schedule download of a single
partsPromises.push(downloadPart(part));
});
// Wait for all parts to resolve
$q.all(partsPromises)
.then(function(data) {
// Returned data will be an array of results from each individual http promise
resData = [];
angular.forEach(data, function(partData) {
//handle each return part
resData.push(partData.data);
})
defer.resolve(resData); // Notify client we downloaded all parts
}, function error(response) { // Handle possible errors
console.log('Error while downloading parts'
response);
defer.reject('Error while downloading parts');
});
return defer.promise;
};
Then, in your client you can simply wait for the downloadAllParts to complete:
downloadAllParts(myParts)
.then(function(data) {
alert('Success!');
}, function(error) {
alert(error);
})
Since $q.all is a promise as well, you can get rid of defers all together:
// Aggregat epromise that downloads all parts
function downloadAllParts(parts) {
var partsPromises = []; // Setup array for indivudual part promises
angular.forEach(parts, function(part) { // Iterate through each part
// Schedule download of a single
partsPromises.push(downloadPart(part));
});
// Wait for all parts to resolve
return $q.all(partsPromises)
.then(function(data) {
// Returned data will be an array of results from each individual http promise
var resData = [];
angular.forEach(data, function(partData) {
//handle each return part
resData.push(partData.data);
})
return resData;
});
};
Here is a working jsfiddle: link
Thanks all! The following code worked for me. I merged the solutions from the comments with some own stuff, and this solution made it to work for me.
// Aggregat epromise that downloads all parts
function downloadAllParts(parts) {
vm.showLoader = true;
var defer = $q.defer(); // Setup return promise
var partsPromises = []; // Setup array for indivudual part promises
angular.forEach(parts, function(part, key) { // Iterate through each part
// Schedule download of a single
if(typeof part.image !== 'undefined' && part.image !== "") {
partsPromises.push(downloadPartImage(part));
}
if(typeof part.audio !== 'undefined' && part.audio !== "") {
partsPromises.push(downloadPartAudio(part));
}
if(typeof part.video !== 'undefined' && part.video !== "") {
partsPromises.push(downloadPartVideo(part));
}
if(key > 0){
vm.tourData.parts[key].available = false;
} else {
vm.tourData.parts[key].available = true;
}
});
// Wait for all parts to resolve
$q.all(partsPromises)
.then(function(data) {
// Returned data will be an array of results from each individual http promise
resData = [];
angular.forEach(data, function(partData) {
//handle each return part
resData.push(partData);
})
defer.resolve(resData); // Notify client we downloaded all parts
}, function error(response) { // Handle possible errors
console.log('Error while downloading parts' + response);
defer.reject('Error while downloading parts');
});
return defer.promise;
}
function downloadPartImage(part) {
var data = {
oid: tourId,
plid: part.image,
func: 'image'
};
return TourFactory.getSynchronousPartImage(part, tourId).then(function(data){
part.partImage = data.data;
return data;
});
};
function downloadPartAudio(part) {
var targetPath = cordova.file.externalDataDirectory + tourId + '/audio/' + part._id.$id + '.mp3';
var url = "https://www.tourtodo.com/gameinfo/" + part.audio;
var trustHosts = true;
var options = {};
return $cordovaFileTransfer.download(url, targetPath, {}, true).then(function (result) {
console.log('Save file on '+targetPath+' success!');
part.audioSrc = targetPath;
return result;
}, function (error) {
console.log('Error Download file');
console.log(JSON.stringify(error));
return error;
}, function (progress) {
console.log((progress.loaded / progress.total) * 100);
});
}
function downloadPartVideo(part) {
var targetPath = cordova.file.externalDataDirectory + tourId + '/video/' + part._id.$id + '.mp4';
var url = "https://www.tourtodo.com/gameinfo/" + part.video;
var trustHosts = true;
var options = {};
return $cordovaFileTransfer.download(url, targetPath, {}, true).then(function (result) {
console.log('Save file on '+targetPath+' success!');
part.videoSrc = targetPath;
return result;
}, function (error) {
console.log('Error Download file');
console.log(JSON.stringify(error));
return error;
}, function (progress) {
console.log((progress.loaded / progress.total) * 100);
});
}
function getAndFillFullTour() {
vm.showLoader = true;
// load data
TourFactory.getFullTour(vm.tourData.number, function(data){
if(data.state == 'success'){
vm.tourData = data;
downloadAllParts(vm.tourData.parts)
.then(function(data) {
vm.showLoader = false;
vm.showStartButton = true;
alertPopup = $ionicPopup.alert({
title: 'Gelukt!',
template: 'De tour is opgehaald. Druk op start om de tour te starten.'
});
localStorage.setItem('tourdata', JSON.stringify(vm.tourData));
console.log(JSON.parse(localStorage.getItem('tourdata')));
}, function(error) {
console.log('error');
console.log(error);
})
} else {
console.log('error');
}
});
}

Angular function in service not seen as a function

I have been trying to move a bunch of code into a service instead of having it sit in the controller because other controllers in my application are going to need some of the same functionality. I have the following controller code:
JBenchApp.controller('CaseListCtrl', ['$scope', '$http', 'HoldState',
function ($scope, $http, HoldState) {
//----------------------------------------------------------
// Load the Calendar Data whent he department changes
//----------------------------------------------------------
$scope.getCalendarOnDeptChange = function () {
// Get the dropdown into e
var e = document.getElementById("deptSelect");
// Set $scope.department to the text of the selected dropdown item --- MUST FIND BETTER ANGULAR METHOD
$scope.department = e.options[e.selectedIndex].text;
console.log($scope.department);
$scope.getCalendar();
};
//----------------------------------------------------------
// Load the calendar data
//----------------------------------------------------------
$scope.getCalendar = function () {
// Retrieve calendar data
HoldState.getCalendar($scope.lastDepartment, $scope.date, $scope.lastLawType, $scope.lastLocationID).then(function (data) {
$scope.cases = data;
$scope.$apply();
});
HoldState.setDepartment($scope.department);
};
//----------------------------------------------------------
// Load the user's default settings
//----------------------------------------------------------
$scope.loadDefaults = function () {
HoldState.getUserDefaults($scope.UserID).then(function (data) {
$scope.UserDefaults = data;
});
$scope.defaultDepartment = $scope.UserDefaults.CourtRoom;
$scope.defaultLawType = $scope.UserDefaults.LitigationCode;
$scope.defaultLocationID = $scope.UserDefaults.LocID;
};
$scope.loadPaths = function () {
HoldState.getTypeOfLaw().then(function (data) {
$scope.lastLawType = data;
});
HoldState.getCourthouse().then(function (data) {
$scope.lastLocationID = data;
});
HoldState.getDepartment().then(function (data) {
$scope.lastDepartment = data;
});
};
$scope.doAuthentication = function () {
$scope.UserID = 'dpeng';
};
$scope.saveSequence = function () {
};
//----------------------------------------------------------
// Initial processing
// Located here so that all functions are defined before
// being called.
// 1. Authenticate the user
// 2. Get the default values
// 3. Load the paths
// 4. Get the list of departments
// 5. Show the calendar.
//----------------------------------------------------------
$scope.doAuthentication();
$scope.loadDefaults();
$scope.loadPaths();
HoldState.getDepartmentList($scope.lastLawType, $scope.lastLocationID).then(function (data) {
$scope.departments = data;
});
$scope.getCalendar();
}]);
I also have the following service code:
var StateService = angular.module('StateService', [])
.service('HoldState', function ($http) {
this.setTypeOfLaw = function (a) { localStorage.setItem('LawType', a) };
this.setCourthouse = function (a) { localStorage.setItem('Building', a) };
this.setDepartment = function (a) { localStorage.setItem('Dept', a) };
this.getTypeOfLaw = function () {
var LT = localStorage.getItem('LawType');
return LT;
};
this.getCourthouse = function () {
var BLDG = localStorage.getItem('Building');
return BLDG;
};
this.getDepartment = function () {
var DEPT = localStorage.getItem('Dept');
return DEPT;
};
this.setStatus = function (a) { localStorage.setItem('Status', a) };
this.getStatus = function () {
var STATUS = localStorage.getItem('Status');
return STATUS;
}
//Begin default settings
this.getUserDefaults = function (UserID) {
var userDefaults = [];
$http.get('http://10.34.34.46/BenchViewServices/api/UserPreference/Default/' + UserID)
.then(function (response) {
userDefaults = response;
var status = this.getStatus();
// If the status is 0 then we have not yet navigated anywhere so we will need to set the path values to be
// the same as the default. We do nothing if status is not 0 because it means we already have path values set
if (status == 0) {
this.setTypeOfLaw(response.LitigationCode);
this.setCourthouse(response.LocID);
this.setDepartment(response.CourtRoom);
}
}, function (response) {
console.log(response.status + " -- " + response.data + " -- " + response.statusText);
});
return userDefaults;
};
When I call $scope.loadDefaults(); I get an error that says:
TypeError: HoldState.getUserDefaults(...).then is not a function
at m.$scope.loadDefaults (http://localhost:54365/js/controllers.js:78:52)
at new <anonymous> (http://localhost:54365/js/controllers.js:121:14)
at Object.e [as invoke] (http://localhost:54365/js/angular.min.js:36:315)
at x.instance (http://localhost:54365/js/angular.min.js:76:79)
at http://localhost:54365/js/angular.min.js:59:85
at q (http://localhost:54365/js/angular.min.js:7:428)
at M (http://localhost:54365/js/angular.min.js:59:69)
at g (http://localhost:54365/js/angular.min.js:51:409)
at http://localhost:54365/js/angular.min.js:51:17
at chrome-extension://ighdmehidhipcmcojjgiloacoafjmpfk/dist/hint.js:2071:22 <div ng-view="" class="view-frame ng-scope">(anonymous function) # angular.min.js:102
angular.min.js:102 TypeError: Cannot read property 'getStatus' of undefined
at controllers.js:311
at angular.min.js:112
at m.$eval (angular.min.js:126)
at m.$digest (angular.min.js:123)
at m.scopePrototype.$digest (chrome-extension://ighdmehidhipcmcojjgiloacoafjmpfk/dist/hint.js:1955)
at m.$apply (angular.min.js:127)
at m.scopePrototype.$apply (chrome-extension://ighdmehidhipcmcojjgiloacoafjmpfk/dist/hint.js:2018)
at l (angular.min.js:81)
at P (angular.min.js:85)
at XMLHttpRequest.H.onload (angular.min.js:86)(anonymous function) # angular.min.js:102
What have I done wrong? I am merely trying to cleanly get back the data from a web service through my Angular service.
getUserDefaults is the only method that really needs to be a promise, as you are making an async call to your api. So, inject $q into your service and then have that method return a promise.
this.getUserDefaults = function (UserID) {
var userDefaults = [], deferred = $q.defer();
$http.get('http://10.34.34.46/BenchViewServices/api/UserPreference/Default/' + UserID)
.then(function (response) {
var status = this.getStatus();
// If the status is 0 then we have not yet navigated anywhere so we will need to set the path values to be
// the same as the default. We do nothing if status is not 0 because it means we already have path values set
if (status == 0) {
this.setTypeOfLaw(response.LitigationCode);
this.setCourthouse(response.LocID);
this.setDepartment(response.CourtRoom);
}
d.resolve(response);
}, function (response) {
console.log(response.status + " -- " + response.data + " -- " + response.statusText);
});
return deferred.promise;
};
you also should just use the getters as getters, and not try to treat them as promises.
i.e.
$scope.loadPaths = function () {
$scope.lastLawType = HoldState.getTypeOfLaw();
$scope.lastLocationID = HoldState.getCourthouse();
$scope.lastDepartment = HoldState.getDepartment();
};
This should fix the problem
$scope.loadDefaults = function () {
HoldState.getUserDefaults($scope.UserID).then(function (data) {
$scope.UserDefaults = data;
}, function(errData) {
//$scope.UserDefaults isn't going to get filled, so do some error handling here.
});
$scope.defaultDepartment = $scope.UserDefaults.CourtRoom;
$scope.defaultLawType = $scope.UserDefaults.LitigationCode;
$scope.defaultLocationID = $scope.UserDefaults.LocID;
};
And
//Begin default settings
this.getUserDefaults = function (UserID) {
//Here we return the promise
return $http.get('http://10.34.34.46/BenchViewServices/api/UserPreference/Default/' + UserID)
.then(function (response) {
userDefaults = response;
var status = this.getStatus();
// If the status is 0 then we have not yet navigated anywhere so we will need to set the path values to be
// the same as the default. We do nothing if status is not 0 because it means we already have path values set
if (status == 0) {
this.setTypeOfLaw(response.LitigationCode);
this.setCourthouse(response.LocID);
this.setDepartment(response.CourtRoom);
}
//Here we fill the data variable
return response;
}, function (response) {
console.log(response.status + " -- " + response.data + " -- " + response.statusText);
});
};
Take another look at the error:
HoldState.getUserDefaults(...).then is not a function
^^^^
HoldState.getUserDefaults() doesn't return a promise, that's the problem.
To be able to consume the service like you are at the moment, tweak the method a little:
this.getUserDefaults = function (UserID) {
return $http.get('http://10.34.34.46/BenchViewServices/api/UserPreference/Default/' + UserID)
.then(function (response) {
var status = this.getStatus();
// If the status is 0 then we have not yet navigated anywhere so we will need to set the path values to be
// the same as the default. We do nothing if status is not 0 because it means we already have path values set
if (status == 0) {
this.setTypeOfLaw(response.LitigationCode);
this.setCourthouse(response.LocID);
this.setDepartment(response.CourtRoom);
}
}, function (response) {
console.log(response.status + " -- " + response.data + " -- " + response.statusText);
});
};
Since promises are chainable, you are essentially returning the promise you get from $http.get(), which will be resolved with response. This should make it work.

How to reorder execution of asynchronous functions?

if (option == 'Follow All') {
for (var i = 0; i < userArray.length; i++) {
followUser(params..);
}
// How to get this part to execute after followUser is done? (Basically when the for loop finishes)
alert("There was a problem processing your request on Twitter to follow the following users: " + $('#errored-users').val());
$('#errored-users').val('');
}
How can I call this first multiple times and wait for it to finish?
var followUser = function(params..) {
$.post('/api/1.0/followUser.php', {
'user_to_follow': username,
'username': user
}).done(function(data) { {
if (!is_batch) {
alert("There was a problem processing your request on Twitter to follow #" + username);
} else {
//This currently gets executed last?
var names = $('#errored-users').val();
if (names == "") {
names = '#' + username + ', ';
} else {
names += '#' + username + ', ';
}
$('#errored-users').val(names);
}
};
Since you are already using jQuery, you can easily use the AJAX requests/promises and wait for all of them to complete. $.when can help you a lot with this:
var followUser = function(params..) {
// return the promise!
return $.post('/api/1.0/followUser.php', { ... });
};
if (option == 'Follow All') {
var promises = [];
for (var i = 0; i < userArray.length; i++) {
promises.push(followUser(...));
}
$.when.apply(null, promises)
.done(function() {
// all users were successfully followed
})
.fail(function() {
// at least one follow failed; no information about the others
alert("There was a problem processing your request...");
$('#errored-users').val('');
});
}
This will call the .done handler when all requests have completed, but it will call the .fail handler as soon as just one has failed.
If instead you want some handler to run after all requests have completed (either success or failure) you 'd need to do it more manually, for example:
var followUser = function(params..) {
// return the promise!
return $.post('/api/1.0/followUser.php', { ... });
};
if (option == 'Follow All') {
var outcomes = { done: [], failed: [] };
var total = userArray.length;
function allFinished() {
return outcomes.done.length + outcomes.failed.length == total;
}
for (var i = 0; i < total; i++) {
followUser(...)
.done(function() {
outcomes.done.push(username);
})
.fail(function() {
outcomes.failed.push(username);
})
// this must come last
.always(function() {
if (allFinished) {
// outcomes contains the results
}
})
}
}
This will still use jQuery's notion of a request having succeeded or failed, which is based on Twitter's HTTP response codes. If you want to customize this behavior you can amend followUser as such:
var followUser = function(params..) {
return $.post('/api/1.0/followUser.php', { ... })
.then(
// first argument handles HTTP response successes, but you can
// convert them to failures here:
function(data) {
if (convertSuccessToFailure) {
return $.Deferred.reject(data);
}
});
};
As of jQuery 1.5 any of the $.ajax family of functions return a promise - and you can combine multiple promises into a new promise that will be resolved when all the child promises are resolved using $.when:
function followUser(/* params */) {
return $.post('/api/1.0/followUser.php', {
user_to_follow: username,
username: user
});
}
var userRequests = [];
for (var i = 0, l = userArray.length; i < l; i++) {
userRequests.push(followUser(/* params */));
}
$.when.apply($, userRequests).then(function(data) { /* etc. */ });
You could define a global variable which holds the number of calls to followUser:
if (option == 'Follow All') {
var countUsers = userArray.length;
for (var i = 0; i < countUsers; i++) {
followUser(params..);
}
}
Then you change the anonymous function to count backwards and execute your last statement if all users are done:
function(data) {
if (!is_batch) {
alert("There was a problem processing your request on Twitter to follow #" + username);
} else {
(...)
}
countUsers--;
if(countUsers == 0){
alert("There was a problem processing your request on Twitter to follow the following users: " + $('#errored-users').val());
$('#errored-users').val('');
}
};
A potential solution for this is to use Promises (see here for an in-depth explanation). It provides a new style of coding in Javascript that effectively enables you to make asynchronous code synchronous. (This is a big simplification of Promises - see here for an article explaining it a little bit more).
There are various implementations which you could use. The one I most use is found here: https://github.com/cujojs/when. The examples provided within it's wiki demonstrates the power of promises (see here).
The basic outline for your code using when.js would look and read something like this:
if (option == 'Follow All') {
var deferreds = [];
for (var i = 0; i < userArray.length; i++) {
deferreds.push(followUser(params..));
}
when.all(deferreds).then(function everythingWasFine(suceededUsernames) {
//do something with the responses e.g.
alert(succeededUsernames.length + ' users were followed');
},
function somethingWentWrong(failedUsernames) {
alert("There was a problem processing your request on Twitter to follow the following users: " + failedUsernames.join(','));
});
}
var followUser = function(params..) {
var defer = when.defer();
$.post('/api/1.0/followUser.php', {
'user_to_follow': username,
'username': user
}).done(function(data) {
if (failure) {
defer.reject(username);
} else {
defer.resolve(username);
}
});
return when.promise;
};

Wait until enclosure's async method completes javascript

I am new to javascript programming. I just can't find an answer that works.
The problem is that my function only works when it is wrapped in setTimeout call like so:
var sPageIdentifier = 'ReportViewer';
UserPreferencesManager.Initialize(sPageIdentifier);
setTimeout(function () {
var strUserPrefs = UserPreferencesManager.GetPreferences();
console.log(strUserPrefs);
initLayout(strUserPrefs);
}, 1000);
function initLayout(strUserPrefs) {
//do stuff using strUserPrefs
}
If I comment out setTimeout function, the initLayout(strUserPrefs) fails because strUserPrefs is null.
Any help will be appreciated!
Here is the UserPreferencesManager.js code:
var UserPreferencesManager = function () {
var strPrefsID = null;
var strPrefsString = null;
return {
Initialize: function (strPrefsIDIn) {
strPrefsID = strPrefsIDIn;
strPrefsString = this.GetPreferences();
},
GetPreferences: function () {
if (!strPrefsID) {
alert("Validation Failed: the UserPreferencesManager must be initialized prior to usage.");
return null;
}
if (!strPrefsString) {
this.LoadPreferences();
return strPrefsString;
}
return strPrefsString;
},
LoadPreferences: function () {
if (!strPrefsID) {
alert("Validation Failed: the UserPreferencesManager must be initialized prior to usage.");
return null;
}
myasyncfunctioncall({
parameters: ["USR_PersonId", "abc", 'GET']
script_name: 'MAINTAIN_USER_PREFS',
onexception: function (exception, xhr, options) {
alert('Error: ' + xhr.statusText + +exception);
console.log(exception);
},
onsuccess: function (data, xhr, options) {
if (data == "User ID is zero") {
alert('MP_MAINTAIN_USER_PREFS: must be > 0.0');
strPrefsString = data;
}
else {
strPrefsString = data;
}
}
});
},// end of LoadPreferences
WritePreferences: function (strPrefsIn, strPrefsID) {
if (strPrefsID && typeof strPrefsID === "string") {
if (strPrefsIn != null) {
myasyncfunctioncall({
parameters: ["USR_PersonId", strPrefsID, strPrefsIn , 'SET']
script_name: 'MAINTAIN_USER_PREFS',
onexception: function (exception, xhr, options) {
alert('Error: ' + xhr.statusText + +exception);
console.log(exception);
},
onsuccess: function (data, xhr, options) {
if (data == "transaction-ok") {
UserPreferencesManager.LoadPreferences();
} else if (data == "User ID is zero") {
alert('MP_MAINTAIN_USER_PREFS: must be > 0.0');
}
}
});
} else {
alert("Error: Preferences object must be initialized prior to writing preferences");
}
} else {
alert('Error: The preference ID can\'t be null and must to be of type string');
return;
}
}// end of WritePreferences
};// end of return API
}(); // end of UserPreferencesManager
Seens like this myasyncfunctioncall is sending an async request. You'll need to add some variable to set if the response of this async request has arrived, and then, when it is set you can continue with your routine.
WHenever an async call is made on javascript, the program continues as if it was already completed. You have to mannually add checks to see if it has completed or not.
UserPreferencesManager.GetPreferences() is Making the asynchronous AJAX call to get the user preferences. So, in this case Javascript thread will continue execution in current thread context and executes initLayout(strUserPrefs). But at this state GetPreferences() call is still not complete and strUserPrefs is null.
SetTimeout is one of the trick to overcome this issue, which you did. But you can also design the APIs in such a way that it allows the callback function execution for each asynchronous AJAX calls.
Thanks for the tip, Balachandra!
Here is what I did, added two parameters to LoadPreferences method - callback and strPrefsID, so I can invoke fnCallback function within on success and pass it ajax data:
LoadPreferences: function (fnCallback, strPrefsID) {
if (!strPrefsID) {
alert("Validation Failed: the BhsUserPreferencesManager must be initialized prior to usage.");
return null;
}
if (strPrefsString) {
// strPrefsString is not null, so return it
callback(strPrefsString);
} else {
myasyncfunctioncall({
parameters: ["USR_PersonId", "abc", 'GET']
script_name: 'MAINTAIN_USER_PREFS',
onexception: function (exception, xhr, options) {
alert('Error: ' + xhr.statusText + +exception);
console.log(exception);
},
onsuccess: function (data, xhr, options) {
if (data == "User ID is zero") {
alert('MAINTAIN_USER_PREFS: must be > 0.0');
strPrefsString = data;
} else if (data.substring(0, 5) === "ERROR") {
alert(data);
} else {
fnCallback(data);
}
}
});
}
}// end of LoadPreferences
And here is how now I can call initLayout:
BhsUserPreferencesManager.LoadPreferences(initLayout, sPageIdentifier);

Categories

Resources