AngularJS: Passing Promise, yet Cannot read property 'finally' of undefined - angularjs

In my angular app, I have 2 methods save() and saveTriggers(). saveTriggers() updates all records by calling a web service (C#). I want to make sure that a block of code is executed after all records are updated in saveTriggers() and control is returned to save(). I believe I need to pass something from the saveTriggers() to make finally block execute. I tried various things, nothing works. Using .then() also gives the same error. I am not that good at JS. Can you please guide me.
vm.updatedTriggers = []; // IDs are pushed in
vm.saveTriggers = function () {
if (vm.updatedTriggers.length === 0) {
vm.close();
} else {
vm.saving = true;
vm.save()
.finally(function () { // ERROR - Cannot read property 'finally' of undefined
console.log("Saved all. Closing..."); // Never REACHES here
vm.saving = false;
vm.updated = true;
$uibModalInstance.close(true);
});
}
};
vm.save = function () {
//vm.saving = true;
for (var i = 0; i < vm.updatedTriggers.length; i++) {
var trigger = vm.triggers.find(t => t.id === vm.updatedTriggers[i]);
var input = {
id: trigger.id,
target: trigger.target,
targetInfo: vm.targetData,
event: trigger.event,
eventQuantity: trigger.eventQuantity,
eventQuantityExtra: trigger.eventQuantityExtra
};
rpmService.editDeviceTrigger(input);
/*.finally(function () {
console.log("Updated event"); // Reaches here
vm.updated = true;
return Promise.resolve(2);
});*/ // Commenting this also doesn't help
}
return Promise.resolve(2);
};
rpmService.editDeviceTrigger(input)
public async Task EditDeviceTrigger(EditDeviceTriggerInput input) {
// calls other methods with await
// Doesn't return anything
}
EDIT: Updated Code: I got rid of the error, but the output is not is expected series.
vm.saveTriggers = function () {
vm.saving = true;
vm.save().then
(function success() {
console.log("Returned Result ");
console.log("Saved all. Closing..."); // These lines are executed before the event is upated
vm.saving = false;
$uibModalInstance.close(true);
});
};
vm.save = function () {
var deferred = $q.defer();
for (var i = 0; i < vm.updatedTriggers.length; i++) {
var trigger = vm.triggers.find(t => t.id === vm.updatedTriggers[i]);
var input = {
id: trigger.id,
....
};
rpmService.editDeviceTrigger(input)
.finally(function () {
console.log("Updated event"); // Successfully updates all events
vm.updated = true;
});
}
deferred.resolve();
return deferred.promise;
};
OUTPUT:
Returned Result
Saved all. Closing...
Updated event
EXPECTED OUTPUT:
Updated event
Returned Result
Saved all. Closing...
Thanks.

Usually you dont need $q.defer-related things, but u can do same using it if u want.
Here I guess you just need to collect all your save promises and return new resulting one using $q.all:
vm.save = function () {
const myAwesomePromises = []
for (var i = 0; i < vm.updatedTriggers.length; i++) {
...
const savePromise = rpmService.editDeviceTrigger(input);
savePromise.finally(() => console.log('edit device finally'));// <-- not sure u need this
myAwesomePromises.push(savePromise);
}
return $q.all(myAwesomePromises).finally(() => console.log('All edit device finally'));
};

Related

dealing with an array of objects with promises

I am trying to make a node express app where I fetch data from different url's making a call to node-fetch to pull the body of some pages and other information about certain url endpoints. I want to then render a html table to display this data through an array of information. I am having trouble with the call to render the information as all the functions are asynchronous making it difficult to make sure all the promise calls have been resolved before making my call to render the page. I have been looking into using bluebird and other promise calls of .finally() and .all() but they don't seem to work on my data as it is not an array of promise calls, but an array of objects. Each object was 4 promise calls to fetch data relating to a column of my table all in one row. Is there a function or specific way to render the page after all promises are resolved?
var express = require('express');
var fetch = require('node-fetch');
fetch.Promise = require('bluebird');
var router = express.Router();
const client = require('../platform-support-tools');
function makeArray() {
var registry = client.getDirectory();
var data_arr = [];
for (var i = 0; i < registry.length; i++) {
var firstUp = 0;
for (var j = 0; i < registry[i]; j++) {
if (registry[i][j]['status'] == 'UP') {
firstUp = j;
break;
}
}
var object = registry[i][firstUp];
data_arr.push({
'name': object['app'],
'status': object['status'],
'swagUrl': object['homePageUrl'] + 'swagger-ui.html',
'swag': getSwag(object),
'version': getVersion(object['statusPageUrl']),
'timestamp': getTimestamp(object['statusPageUrl']),
'description': getDescription(object['healthCheckUrl'])
});
}
return data_arr;
}
function getSwag(object_in) {
var homeUrl = object_in['homePageUrl'];
if (homeUrl[homeUrl.length - 1] != '/'){
homeUrl += '/';
}
var datum = fetch(homeUrl + 'swagger-ui.html')
.then(function (res) {
return res.ok;
}).catch(function (err) {
return 'none';
});
return datum;
}
function getVersion(url_in) {
var version = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['version'];
}).catch(function (error) {
return 'none';
});
return version;
}
function getTimestamp(url_in) {
var timestamp = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['timestamp'];
}).then(function (res) {
return body['version'];
}).catch(function (error) {
return 'none';
});
return timestamp;
}
function getDescription(url_in) {
var des = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['description'];
}).catch(function (error) {
return 'none';
});
return des;
}
/* GET home page. */
router.get('/', function (req, res, next) {
var data_arr = makeArray();
Promise.all(data_arr)
.then(function (response) {
//sorting by app name alphabetically
response.sort(function (a, b) {
return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0);
});
res.render('registry', {title: 'Service Registry', arr: response})
}).catch(function (err) {
console.log('There was an error loading the page: '+err);
});
});
To wait on all those promises, you will have to put them into an array so you can use Promise.all() on them. You can do that like this:
let promises = [];
for (item of data_arr) {
promises.push(item.swag);
promises.push(item.version);
promises.push(item.timestamp);
promises.push(item.description);
}
Promise.all(promises).then(function(results) {
// all promises done here
})
If you want the values from all those promises, back into the object that's a bit more work.
let promises = [];
for (item of data_arr) {
promises.push(item.swag);
promises.push(item.version);
promises.push(item.timestamp);
promises.push(item.description);
}
Promise.all(promises).then(function(results) {
// replace promises with their resolved values
let index = 0;
for (let i = 0; i < results.length; i += 4) {
data_arr[index].swag = results[i];
data_arr[index].version = results[i + 1];
data_arr[index].timestamp = results[i + 2];
data_arr[index].description = results[i + 3];
++index;
});
return data_arr;
}).then(function(data_arr) {
// process results here in the array of objects
});
If you had to do this more often that just this once, you could remove the hard coding of property names and could iterate all the properties, collect the property names that contain promises and automatically process just those.
And, here's a more general version that takes an array of objects where some properties on the objects are promises. This implementation modifies the promise properties on the objects in place (it does not copy the array of the objects).
function promiseAllProps(arrayOfObjects) {
let datum = [];
let promises = [];
arrayOfObjects.forEach(function(obj, index) {
Object.keys(obj).forEach(function(prop) {
let val = obj[prop];
// if it smells like a promise, lets track it
if (val && val.then) {
promises.push(val);
// and keep track of where it came from
datum.push({obj: obj, prop: prop});
}
});
});
return Promise.all(promises).then(function(results) {
// now put all the results back in original arrayOfObjects in place of the promises
// so now instead of promises, the actaul values are there
results.forEach(function(val, index) {
// get the info for this index
let info = datum[index];
// use that info to know which object and which property this value belongs to
info.obj[info.prop] = val;
});
// make resolved value be our original (now modified) array of objects
return arrayOfObjects;
});
}
You would use this like this:
// data_arr is array of objects where some properties are promises
promiseAllProps(data_arr).then(function(r) {
// r is a modified data_arr where all promises in the
// array of objects were replaced with their resolved values
}).catch(function(err) {
// handle error
});
Using the Bluebird promise library, you can make use of both Promise.map() and Promise.props() and the above function would simply be this:
function promiseAllProps(arrayOfObjects) {
return Promise.map(arrayOfObjects, function(obj) {
return Promise.props(obj);
});
}
Promise.props() iterates an object to find all properties that have promises as values and uses Promise.all() to await all those promises and it returns a new object with all the original properties, but the promises replaced by the resolved values. Since we have an array of objects, we use Promise.map() to iterate and await the whole array of those.

Angular template won't load. Even with $loaded. Data resolves after Load

Using AngularFire, Angular, Firebase.
I load a list of users from a Firebase Database. I use $loaded to ensure it waits until data loads.
I take this list, compare it against another firebase database of groups and push the results into two arrays.
Based on the console.logs the data sorts correctly. However, inside my template I get a blank page (I think this is because the page loads before the data is sorted).
Thoughts?
let userLoggedIn = AuthFactory.getUser();
var allUsersArray = $firebaseArray(ConnectFactory.fbUserDb);
var x = firebase.database().ref('groups');
var friendArr = [];
var notFriendArr = [];
allUsersArray.$loaded().then(function(){
angular.forEach(allUsersArray, function(user, i) {
var haveIAdded = x.child(userLoggedIn).child(allUsersArray[i].uid).once('value').then(function (snap) {
if (snap.val() !== null) {
return true;
} else {
return false;
}
});
var haveTheyAdded = x.child(allUsersArray[i].uid).child(userLoggedIn).once('value').then(function (snap) {
if (snap.val() !== null) {
return true;
} else {
return false;
}
});
Promise.all([haveIAdded, haveTheyAdded]).then(function([you, they]) {
if (you && they) {
console.log('We Are Friends', allUsersArray[i]);
friendArr.push(allUsersArray[i]);
} else {
console.log('not a friend ', allUsersArray[i]);
notFriendArr.push(allUsersArray[i]);
}
});
});
$scope.friendList = friendArr;
$scope.notFriendList = notFriendArr;
});
Alright, this time I tried to actually read the question before attempting to answer. ;-)
When you set your $scope.friendList and $scope.notFriendList within the $loaded promise, your Promise.all may (and most likely) havn't resolved yet when those are called, since angular.forEach doesn't wait for the promises to finish before moving on to the next statement in the function. So you'll have to build an array of promises and wait for them all to resolve outside of the loop before attempting to set your $scope variables.
allUsersArray.$loaded().then(function(){
var promises = [];
var friendArr = [];
var notFriendArr = [];
angular.forEach(allUsersArray, function(user, i) {
... // Same as before
promises.push(
Promise.all([haveIAdded, haveTheyAdded]).then(function([you, they]) {
if (you && they) {
console.log('We Are Friends', allUsersArray[i]);
friendArr.push(allUsersArray[i]);
} else {
console.log('not a friend ', allUsersArray[i]);
notFriendArr.push(allUsersArray[i]);
}
})
);
});
Promise.all(promises).then(function(){
$scope.friendList = friendArr;
$scope.notFriendList = notFriendArr;
});
});

AngularJS chaining promises - need to do work before the next 'then'

I am working on a promise chain. The first call is an $http call to check if a user exists, and then if it does, theres a bunch of .then() statements that run sequentially.
My question is this.. in that first call, i don't want to return the promise of the $http request because if the user doesn't exist, the results are just an empty array and the promise resolves, thus triggering the next action to look up information about the user. I wrote the following code...
(see the part in comments about being the important part i'm asking about)
$scope.checkIfUserExists = function() {
if (angular.isObject($scope.admin.Inductee.Contactor)) {
var handleFault = function( fault ) {
if (typeof(fault) === 'string') {
switch (fault.toUpperCase()){
case 'NODATA':
// Go ahead an save
$scope.pushInductee();
break;
case 'STATUS':
// just get the 'duplicate records check' sign off of there
// The save button is disabled by the critical error
$scope.hideSave = false;
break;
case 'ASSIGNED':
// just get the 'duplicate records check' sign off of there
// The save button is disabled by the critical error
$scope.hideSave = true;
break;
default:
$log.error(fault);
$location.path('/error/default');
}
} else {
$log.error(fault);
$location.path('/error/default');
}
};
$scope.getMatchingIndData()
.then($scope.procBusLogic)
.then($scope.pushInductee)
.catch(handleFault);
}
};
////HERE IS THE IMPORTANT PART I AM ASKING ABOUT
$scope.getMatchingIndData = function() {
var deferred = $q.defer();
var locals = {};
var checkUser = function(dupeJson){
var checkUserDeferred = $q.defer();
// abandoned promise replaced with my own
sttiJoinDataFactory.checkIfUserExistsNurseleader(dupeJson)
.then(function(results) {
var data = results.data;
if (angular.isArray(data) && data.length > 0){
var highestMatch = data[0];
for (var i = 0; i < data.length; i++) {
if (parseInt(data[i].Score) > parseInt(highestMatch.Score)) {
highestMatch = data[i];
}
}
checkUserDeferred.resolve(highestMatch);
} else {
// Reject the 'overall' promise here
// to effectively break the chain
return deferred.reject('NODATA');
}
})
.catch(function(fault) {
// Any other failure should break the chain
// of http requests at this point
return deferred.reject(fault);
});
return checkUserDeferred.promise;
},
loadindividual = function (highestMatch) {
return $http stuff about the highestmatch
// set data in locals
},
parallelLoadStatusAndInducteeData = function(individual) {
return another $http promise based on the last then()
// set data in locals
},
loadCeremonyData = function (inductees){
return another $http promise based on the last call then() // set data in locals
},
reportProblems = function( fault ) {
deferred.reject(fault);
};
checkUser($scope.generateDupJson())
.then(loadindividual, reportProblems)
.then(parallelLoadStatusAndInducteeData, reportProblems)
.then(loadCeremonyData, reportProblems)
.then(function() {
deferred.resolve(locals);
})
.catch( reportProblems );
return deferred.promise;
};
Must I take into account the abandoned promise, since I really need to promise to resolve when the data comes back, and i need to reject it if there is NODATA. This is handled in the calling function's chain.
Also, I'm aware of antipatterns here. I'm trying my best to not nest promises, maintain the chain, as well as handle exceptions.
Ok I have a few comments for you:
...
// revert if and return immediately
// to reduce indentation
if (typeof(fault) !== 'string') {
$log.error(fault);
$location.path('/error/default');
return;
}
switch (fault.toUpperCase()) {
...
You don't need deferred objects:
var checkUser = function(dupeJson){
// this is not abandoned because we are returning it
return sttiJoinDataFactory.checkIfUserExistsNurseleader(dupeJson)
.then(function(results) {
var data = results.data;
if (!angular.isArray(data) || data.length <= 0) {
return $q.reject('NODATA');
}
var highestMatch = data.reduce(function (highest, d) {
return parseInt(d.Score) > parseInt(highest.Score) ?
d : highest;
}, data[0]);
return highestMatch;
}); // you don't need catch here if you're gonna reject it again
}
...
checkUser(...)
// loadIndividual will be called
// after everything inside checkUser resolves
// so you will have your highestMatch
.then(loadIndividual)
.then(parallelLoadStatusAndInducteeData)
.then(loadCeremonyData)
// you don't need to repeat reportProblems, just catch in the end
// if anything rejects prior to this point
// reportProblems will be called
.catch(reportProblems)
...

Stuck in async loop with wrapAsync

My goal is to go through a loop asynchronously:
client.js:
abc = function() {
for (var i = 0; i <= 49; i++) {
console.log(i);
Meteor.call('testBla', i)
}
}
server.js
testBla: function(i) {
function asyncCall() {
console.log('inside asyncCall', i)
return 'done';
}
var syncCall = Meteor.wrapAsync(asyncCall);
console.log('a');
var res = syncCall(i);
console.log('b')
return res;
}
Console:
a
inside asyncCall 0
Why does it stuck?
Functions you can pass to Meteor.wrapAsync must have a specific signature : their arguments must end with a callback given 2 arguments : error and result.
Inside an async function body, you must invoke the callback with either an error in case the function fails, or the result if everything is OK.
function asyncHelloWorld(callsCount, callback){
// simulate fake error every 5 calls
if(callsCount % 5 === 0){
callback("error");
}
callback(null,);
}
for(var i = 0; i < 50; i++){
asyncHelloWorld(i, function(error, result){
if(error){
console.log(error.reason);
return;
}
console.log(result);
});
}
You can only wrap functions that respect this signature and behavior, which is a standard inherited from Node.JS.
When you wrap async functions, don't forget to use a try/catch block if you want to handle the potential error.
Meteor.methods({
helloWorld: function(i){
var syncHelloWorld = Meteor.wrapAsync(asyncHelloWorld);
console.log("a");
try{
var res = syncHelloWorld(i);
console.log("b")
return res;
}
catch(exception){
console.log(exception);
console.log("c");
// do not recover, propagates the exception back to the client (standard behavior)
throw exception;
}
}
});

binding to service variable, doesnt refresh (service changes the var frequently)

In my Service i have the vars i want to display and the getters for it:
var docsLoaded = 0;
var docsToLoad = null;
pouchService.getDocsLoaded = function () {
return docsLoaded;
};
pouchService.getDocsToLoad = function () {
return docsToLoad;
};
While the service is syncing, i want to count the synced docs
pouchService.syncNow = function () {
var foundLastSeq = false;
docsLoaded = 0;
docsToLoad = null;
remoteDB.info().then(function (remoteInfo) {
function findOutDiff(localPosition) {
docsToLoad = (remoteInfo.update_seq - localPosition) + 1;
console.log("docs to load: " + docsToLoad);
}
// start Sync progress
sync = localDB.sync(remoteDB, {live: false})
.on('change', function (info) {
console.log('AI change: ');
console.log(info);
if (info.direction === 'pull') {
if (foundLastSeq === false) {
foundLastSeq = true;
findOutDiff(info.change.last_seq);
}
}
console.log(docsLoaded + " from " + docsToLoad);
docsLoaded++;
})
In my HTML i want to display the progress like this:
{{pouchService.getDocsLoaded()}} from {{pouchService.getDocsToLoad()}}
Now i get sometimes a value from getDocsLoaded, but mostly its zero. When I cancel the Syncprogress i get the value where it's stopped.
So i get the value before it really starts and when it's over, but i want it during the sync progress. (on the console my my progressinfos are working as expected)
Any ideas?
The problem is in applying scope. Jim wrote a nice article about this problem:
jimhoskins.com/2012/12/17/angularjs-and-apply.html
Solved it:
$rootScope.$apply(function () {
docsLoaded++;
});

Resources