Angularjs promise not resolving in time - javascript

I am trying to retrieve data for use in a service that can be used throughout my app. The problem is that I can't get the data to be resolved in time for the routine that uses it. Here is the routine that uses it:
function getTranslation(lookup, language) {
var i = 0;
if (vm.translations == null) {
//vm.translations = getAllTranslations();
dataService.getTranslations()
.then(function (data) {
vm.translations = data;
});
}
var len = vm.translations.length;
for (var i=0; i < len; i++) {
if (vm.translations[i].labelName == lookup) {
if (language == "English") {
return vm.translations[i].english;
} else {
if (language == "Spanish") {
return vm.translations[i].espanol;
}
}
}
}
return null;
}
Here is the calling method within that service:
function getAllTranslations() {
return vm.translations = dataService.getTranslations()
.then(function (data) {
vm.translations = data;
return vm.translations;
});
}
And here is the method in the dataService:
function getTranslations() {
return $http.get('/api/labeltext')
.then (getTranslationComplete)
.catch(getTranslationFailed);
function getTranslationComplete(response) {
var deferred = $q.defer();
return response.data;
}
function getTranslationFailed(error) {
alert("XHR failed for frequent pawner report: " + error.responseText);
}
}
I am still learning angularjs and want to be able to populate the data in the service and then call it from other controllers. However, when I get to my for loop the array is empty and only gets populated after its completed.

That is because the promise will not be resolved before your for loop fires. By placing the loop within the .then(), you will have access to the response and your loop values will be defined. This is not DRY since there would be code duplication if you add an else to the function and would have to add in the same loop code. For that, I would refactor the loop into an external function and just call it from within the proper areas of getTranslation().
function getTranslation(lookup, language) {
var i = 0;
if (vm.translations == null) {
//vm.translations = getAllTranslations();
dataService.getTranslations()
.then(function (data) {
vm.translations = data;
var len = vm.translations.length;
for (var i=0; i < len; i++) {
if (vm.translations[i].labelName == lookup) {
if (language == "English") {
return vm.translations[i].english;
} else {
if (language == "Spanish") {
return vm.translations[i].espanol;
}
}
}
}
});
}
return null;
}

Since your data originates from a promise ($http), all of your subsequent code that needs to access that data has to be within a then function.
angular.controller('myController', function(dataService) {
var translationsPromise;
/**
* Caches translations from /api/labeltext and performs a lookup
* #param lookup
* #param language
*/
function getTranslation(lookup, language) {
if (translationsPromise == null) {
translationsPromise = dataService.getTranslations()
}
translationsPromise.then(function(data) {
vm.translations = data;
var len = vm.translations.length;
for (var i = 0; i < len; i++) {
if (vm.translations[i].labelName == lookup) {
if (language == "English") {
return vm.translations[i].english;
} else {
if (language == "Spanish") {
return vm.translations[i].espanol;
}
}
}
}
});
}
getTranslation('Some Label', 'English').then(function(translation) {
// The translation that was found is accessible in this block
});
});

When data is asynchronously derived, it's always better to cache a promise rather than the data. Thus, should another request for the same data be made before the original promise is resolved, then that promise can be retrieved from cache, and returned or otherwise exploited. The same is not true of cached async data, which is not guaranteed to have arrived when another request is made.
function getTranslation(lookup, language) {
if (!vm.translationsPromise) {
vm.translationsPromise = dataService.getTranslations();
}
return vm.translationsPromise.then(function (data) {
var lang = {'English':'english', 'Spanish':'espanol'};
return data.reduce(function(str, item) {
return (str !== '') ? str : (item.labelName == lookup) ? item[lang[language]] : str;
}, '');
});
}

Related

Node.js Promises within promises not waiting for for loop to return data

The return Promise.all([photoArray]) returns an empty array, seemingly not waiting for the callFB to return its promise that then pushes into the array.
I am not sure what I am doing wrong but am relatively new to Promises with for loops and Ifs.
I am not sure exactly if I am using the correct number of Promises but I seem to not be able to get the 3rd tier Promise.all to wait for the for loop to actually finish (in this scenario, the for loop has to look through many item so this is causing an issue where it is not triggering callFeedback for all the items it should before context.done() gets called.
I have tried using Q.all also for the Promise.all([photoArray]) but have been unable to get that working.
module.exports = function (context, myBlob) {
var res = myBlob
var promiseResolved = checkPhoto(res,context);
var promiseResolved2 = checkVideo(res,context);
Promise.all([promiseResolved, promiseResolved2]).then(function(results){
context.log(results[0], results[1]);
// context.done();
});
});
};
};
function checkPhoto(res, context){
return new Promise((resolve, reject) => {
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
var callFB = callFeedback(context, feedbackId);
Promise.all([callFB]).then(function(results){
photoArray.push(results[0]);
});
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return Promise.all([photoArray]).then(function(results){
context.log("end results: " + results);
resolve(photoArray);
});
} else {
resolve('No photos');
}
})
}
function checkVideo(res, context){
return new Promise((resolve, reject) => {
same as checkPhoto
})
}
function callFeedback(context, feedbackId) {
return new Promise((resolve, reject) => {
var requestUrl = url.parse( URL );
var requestBody = {
"id": feedbackId
};
// send message to httptrigger to message bot
var body = JSON.stringify( requestBody );
const requestOptions = {
standard
};
var request = https.request(requestOptions, function(res) {
var data ="";
res.on('data', function (chunk) {
data += chunk
// context.log('Data: ' + data)
});
res.on('end', function () {
resolve("callFeedback: " + true);
})
}).on('error', function(error) {
});
request.write(body);
request.end();
})
}
The code suffers from promise construction antipattern. If there's already a promise (Promise.all(...)), there is never a need to create a new one.
Wrong behaviour is caused by that Promise.all(...).then(...) promise isn't chained. Errors aren't handled and photoArray.push(results[0]) causes race conditions because it is evaluated later than Promise.all([photoArray])....
In case things should be processed in parallel:
function checkPhoto(res, context){
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
var callFB = callFeedback(context, feedbackId);
// likely no need to wait for callFB result
// and no need for Promise.all
photoArray.push(callFB);
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return Promise.all(photoArray); // not [photoArray]
} else {
return 'No photos';
};
}
callFB promises don't depend on each other and thus can safely be resolved concurrently. This allows to process requests faster.
Promise.all serves a good purpose only if it's used to resolve promises in parallel, while the original code tried to resolve the results (results[0]).
In case things should be processed in series the function benefits from async..await:
async function checkPhoto(res, context){
if (res.photos.length > 0) {
var photoArray = [];
for (var j = 0; j < res.photos.length; j++) {
if (res.photos[j].feedbackId !== null){
var feedbackId = res.photos[j].feedbackId;
const callFBResult = await callFeedback(context, feedbackId);
// no need for Promise.all
photoArray.push(callFBResult);
} else {
photoArray.push("Photo " + j + " has no feedback");
}
}
return photoArray; // no need for Promise.all, the array contains results
} else {
return 'No photos';
};
}
Add try..catch to taste.

AngularJS - return value of a promise ID field in a factory

I am recovering all data in allEnquetes. I just find the requested this list, but an error occurs because the allEnquetes returns a promise.
.factory('enquetesAPI', function($http,myConfig){
return {
allEnquete: function(){
return $http.get(myConfig.remote + '/lists.asp?acao=list-enquete&codadmin='+myConfig.codadmin);
},
getEnquete: function(enqueteId) {
for (var i = 0; i < this.allEnquete().length; i++) {
if (this.allEnquete()[i].codigo === parseInt(enqueteId)) {
return this.allEnquete()[i];
}
}
return this.allEnquete()
}
};
})
This is the result when I console.log ( this.allEnquete )
allEnquetes returns a promise so you just can't loop through that. Check below code. I have not tested it. but this should give you some idea on how you should ideally be doing this.
.factory('enquetesAPI', function($http, $q, myConfig){
return {
allEnquete: function(){
console.log('called');
return $http.get(myConfig.remote + '/lists.asp?acao=list-enquete&codadmin='+myConfig.codadmin);
},
getEnquete: function(enqueteId) {
var deferred = $q.defer();
this.allEnquete().then(function(enquetes){
var returnVal = enquetes;
for (var i = 0; i < enquetes.length; i++) {
if (this.allEnquete()[i].codigo === parseInt(enqueteId)){
returnVal = enquetes[i];
}
}
deferred.resolve(returnVal);
}, function(errorResponse){
deferred.reject(errorResponse);
});
return deferred.promise;
}
};
});
and this is how you should be using your service.
enquetesAPI.getEnquete(id).then(enquetes){
// do whatever you need to do
}

How to pass a promise to a controller in AngularJS

I'm having trouble understanding promises. I have a function $scope.startJob that when called, it calls areThereAvailableSegments in the SegmentsService. The idea is that areThereAvailableSegments should return true or false, so that I can handle this behavior in the controller, but not sure how to do this.
areThereAvailableSegments calls getSegments(), which requests text segments from the database. getSegments returns a promise, and I'm trying to handle this promise with .then in areThereAvailableSegments, but not sure how to pass to the controller.
Here's my Segments factory, that returns the $resource object, segmentsfactory.js:
angular.module('appApp')
.factory('SegmentsFactory', function ($resource) {
return $resource('http://localhost:3000/api/v1/segments/:id');
});
Here's my Segments service, segmentsservice.js:
angular.module('appApp')
.service('SegmentsService', function (SegmentsFactory) {
// gets segments from mongo
this.getSegments = function(jobId) {
return SegmentsFactory.query({ job_id: jobId }).$promise;
};
// should return true/false if there are available segments
this.areThereAvailableSegments = function(jobId) {
var dump = [];
// queries for all segments
this.getSegments(jobId)
.then(function(segments) {
// dumps segments status in a temp array
for (var i = 0; i < segments.length; i++) {
dump.push(segments[i].status);
}
// returns true if there is an 'available' in the temp array
if (dump.indexOf('available') >= 0) {
return true;
} else {
return false;
}
});
};
});
And here's my controller, main.js:
$scope.startJob = function() {
if (SegmentsService.areThereAvailableSegments(jobId)) {
console.log('there are available segments, then trigger texteditor');
} else {
console.log('no segments to translate');
}
};
When executing, console shows "no segments to translate", because SegmentsService is not returning true or false.
How to solve this?
the function areThereAvailibleSegments should return a promise as well.
And then in your controller you have a then case and have your if-else there.
this.areThereAvailableSegments = function(jobId) {
var dump = [];
// queries for all segments
return this.getSegments(jobId) //Return here
.then(function(segments) {
// dumps segments status in a temp array
for (var i = 0; i < segments.length; i++) {
dump.push(segments[i].status);
}
// returns true if there is an 'available' in the temp array
if (dump.indexOf('available') >= 0) {
return true;
} else {
return false;
}
});
};
since your success function returns true or false the promise from areThereAvailibleSegments-function will have that boolean.
Then in you controller:
SegmentsService.areThereAvailableSegments(jobId).then(function(available){
if (available) {
console.log('there are available segments, then trigger texteditor');
} else {
console.log('no segments to translate');
}
});
Theres no reason to have both the service and factory. Remove the factory and move that code into the service. As for your problem, you will have to use the $q module to return a promise. https://docs.angularjs.org/api/ng/service/$q
this.areThereAvailableSegments = function(jobId) {
var dump = [];
var deferred = $q.deferred;
// queries for all segments
this.getSegments(jobId)
.then(function(segments) {
// dumps segments status in a temp array
for (var i = 0; i < segments.length; i++) {
dump.push(segments[i].status);
}
// returns true if there is an 'available' in the temp array
if (dump.indexOf('available') >= 0) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
});
return deferred.promise;
};
You will then have to deal with the promise in the controller.
I would set it up like this...complete the promise in your factory and then call the data in the controller.
In your services js:
.factory('SegmentsFactory', function ($resource) {
var APIRequest = {};
APIRequest.getSegments = function(id){
segmentsAPI = $resource('http://localhost:3000/api/v1/segments/'+id'', ;
return segmentAPI.get().$promise.then(function(segments){
console.log(segments);
return segments;
});
};
return APIRequest;
}]);
And then in your controller:
var dump = [];
$scope.startJob = function() {
if (SegmentsService.areThereAvailableSegments) {
APIRequest.getSegments($scope.id).then(function(data){
if (data.error){
//error stuff here
} else {
if (data.segments.length > 0){
for(var i=0; i < segments.length; i++){
console.log(segments);
dump.push(segments[i].status);
}
};

NodeJS - Deferred function inside a loop

I have a conceptual problem with NodeJS... I need to call a deferred function within a loop and use the response for the next iteration. How can I do that without blocking? Here is what I did but of course, response.rev doesn't get updated for the next iteration:
first.function().then(function(response) {
for (var int = 0; int < $scope.files.length; int++) {
call.function(response.rev).then(function (res) {
response.rev = res.rev;
}, function (reason) {
console.log(reason);
});
}
});
Edit, my real code with Benjamin's help (still not working):
pouchWrapper.updateClient(clientManager.clientCleaner($scope.client)).then(function(response) {
if ($scope.files != null) {
var p = $q.when();
for (var int = 0; int < $scope.files.length; int++) {
var doc = $scope.files[int].slice(0, $scope.files[int].size);
p = p.then(function(formerRes){
return pouchWrapper.uploadFiletoDoc($scope.client._id, $scope.contract.policy_number + '&' + $scope.damage.number + '-' + $scope.files[int].name, res.rev, doc, $scope.files[int].type).then(function (res) {
return res;
}, function (reason) {
console.log(reason);
});
});
}
return p;
}
});
You use .then for that. .then is the asynchronous version of the semicolon:
first.function().then(function(response) {
var p = $q.when(); // start with an empty promise
for (var int = 0; int < $scope.files.length; int++) {
p = p.then(function(formerValue){
return call.function(formerValue).then(function (res) {
// use res here as the _current_ value
return res; // return it, so that the next iteration can use it;
});
});
}
return p; // this resolves with the _last_ value
});

Callbacks in node.js

I've read and heard that I shouldn't return values in functions because it is a blocking operation and that it will potentially refuse any requests until the operation has finished.
So here's a small function I've coded and I'd like to know if I am handling it correctly. I'm saying this because I just started using node and I want to code in the correct way, also because it feels weird to have a testing condition inside the function and another one to test the callback.
function isWithinSplit(path, target, separator, callBack)
{
var response = "";
var readStream = fs.createReadStream(path);
readStream.on('data', function (data) {
response += data;
});
//Data complete, process it
readStream.on('end', function (close)
{
var array = response.split(separator);
for (var idx=0 ; idx < array.length; idx++)
{
if(array[idx] != "" && array[idx] == target)
callBack("true");
else
callBack("false");
}
});
}
Call:
fileHelper.isWithinSplit(__dirname + ROOM_LIST_PATH, "hello", "|", function(data){
if(data == "true")
console.log("hurray!");
});
I just want to know if this is how people do and if it's efficient.
You forgot
error handling
using ===
caching array length
naming anonymous functions
function isWithinSplit(path, target, separator, callBack) {
var response = "";
var readStream = fs.createReadStream(path);
readStream.on('data', function _aggregateData(data) {
response += data;
});
//Data complete, process it
readStream.on('end', function _findTarget(close) {
var array = response.split(separator);
for (var idx = 0, len = array.length; idx < len; idx++) {
if (array[idx] === target) {
return callBack(null, true);
}
}
callback(null, false);
});
readStream.on('error', callBack);
}
fileHelper.isWithinSplit(__dirname + ROOM_LIST_PATH, "hello", "|", printIfSuccess);
function printIfSuccess(err, data) {
if (data === true) {
console.log("hurray!");
}
}
You can also improve it using Array.prototype.any
readStream.on('end', function(close) {
callback(null, response.split(seperator).any(function _doesItMatchTarget(val) {
return val === target;
}));
});

Categories