Angular Then doesn't trigger for Promise on Recursive Function - javascript

Just when I thought I had promises figured out, I'm stumped again. I am trying to use a recursive function to return a promise. It looks like it is working but the "then" portion never gets hit. I tried using $q.all but that is causing me a problem with multiple calls to my Web API. Rewriting the code to use recursion seemed like the answer, but I cannot get the "then" to execute. I figure that I must be missing something simple but I can't seem to figure out just what.
Here is the call to the function:
getClusterLink(linkcodes, returnString)
.then(function () {
value.vchTextBeforeQuestionCluster = $scope.returnString;
})
Here is the recursive function:
function getClusterLink(linkcodes, returnString) {
var deferred = $q.defer();
$scope.returnString = returnString;
if (linkcount < linkcodes.length) {
contractorService.gethyperlink(linkcodes[linkcount])
.success(function (data) {
var vchUrl = data[0].vchUrl;
var end = vchUrl.length;
var docID = vchUrl.substring(vchUrl.indexOf("=") + 1, end);
var vchLinkName = data[0].vchLinkName;
var yay = '' + vchLinkName + '';
var yCode = "|Y" + linkcodes[linkcount] + "~";
$scope.returnString = $scope.returnString.replaceAll(yCode, yay);
linkcount++;
return getClusterLink(linkcodes, $scope.returnString);
})
}
else {
deferred.resolve();
return deferred.promise;
}
};
The function itself works correctly. It hits the resolve and the return deferred.promise, but the "then" never fires.
Any assistance is greatly appreciated!

promise has to be returned by the function before resolve or rejecting it.
function getClusterLink(linkcodes, returnString) {
var deferred = $q.defer();
$scope.returnString = returnString;
if (linkcount < linkcodes.length) {
contractorService.gethyperlink(linkcodes[linkcount])
.success(function (data) {
var vchUrl = data[0].vchUrl;
var end = vchUrl.length;
var docID = vchUrl.substring(vchUrl.indexOf("=") + 1, end);
var vchLinkName = data[0].vchLinkName;
var yay = '' + vchLinkName + '';
var yCode = "|Y" + linkcodes[linkcount] + "~";
$scope.returnString = $scope.returnString.replaceAll(yCode, yay);
linkcount++;
})
return getClusterLink(linkcodes, $scope.returnString);
}
else {
deferred.resolve();
}
return deferred.promise;
};
.then is implemented on promise object. So as the function is returning the promise ,.then would work fine.
You can look at this sample https://jsbin.com/daseyu/edit?html,js,console,output
It works fine.
I think the problem is because you are returning getClusterLink in success. You can return in end of if loop and not in .success.
Hope this helps.

Your getClusterLink function does not return a promise in the case where contractorService.gethyperlink is called. I wonder you don't get an exception from that. And even if you always returned deferred.promise, it wouldn't be resolved in that branch.
But you should not use a deferred at all here. Just use $q.resolve, and chain onto the $http promise that gethyperlink returns. Notice that .success is deprecated, and does no chaining like then does - returning from that callback is pointless.
function getClusterLink(linkcodes, returnString) {
$scope.returnString = returnString;
if (linkcount < linkcodes.length) {
return contractorService.gethyperlink(linkcodes[linkcount])
// ^^^^^^
.then(function (data) {
// ^^^^^
var vchUrl = data[0].vchUrl;
var end = vchUrl.length;
var docID = vchUrl.substring(vchUrl.indexOf("=") + 1, end);
var vchLinkName = data[0].vchLinkName;
var yay = '' + vchLinkName + '';
var yCode = "|Y" + linkcodes[linkcount] + "~";
$scope.returnString = $scope.returnString.replaceAll(yCode, yay);
linkcount++;
return getClusterLink(linkcodes, $scope.returnString);
});
} else {
return $q.resolve();
}
}

I figured it out. Seems the problem was that I had var deferred = $q.defer() inside of the recursive function so it kept resetting the variable. Moving it outside of the function (like below) resolved the issue and the "then" now fires.
var thisdeferred = $q.defer();
function getClusterLink(linkcodes, returnString) {
if (linkcount < linkcodes.length) {
contractorService.gethyperlink(linkcodes[linkcount])
.success(function (data) {
var vchUrl = data[0].vchUrl;
var end = vchUrl.length;
var docID = vchUrl.substring(vchUrl.indexOf("=") + 1, end);
var vchLinkName = data[0].vchLinkName;
var yay = '' + vchLinkName + '';
var yCode = "|Y" + linkcodes[linkcount] + "~";
$scope.returnString = $scope.returnString.replaceAll(yCode, yay);
linkcount++;
return getClusterLink(linkcodes, $scope.returnString);
})
}
else {
thisdeferred.resolve();
}
return thisdeferred.promise;
};

Related

How to make async request in cycle?

var userName = 'realgrumpycat';
var moreAvailable = true;
var lastId = '';
while (moreAvailable)
{
getPhotosDataFromRequest(userName, lastId).then(function (data)
{
moreAvailable = data.more_available;
lastId = data[data.length - 1].id;
console.log(data);
});
}
getPhotosDataFromRequest() returns new Promise() and JSON with data. I'd like to execute this method several times at cyscle. But as I see at debugger, while loop executes so fast, that doesn't step into promise then block
Try using function recursion:
var userName = 'realgrumpycat';
var lastId = '';
var getPhotos = function()
{
getPhotosDataFromRequest(userName, lastId).then(function (data)
{
lastId = data[data.length - 1].id;
console.log(data);
if (data.more_available)
{
getPhotos();
}
});
};
getPhotos();
just as iterative alternative (as concept), but not really a solution in real life, because of performance and limits:
//i try to use here es5 only
var userName = 'realgrumpycat';
var moreAvailable = true;//<-- can be removed
var lastId = ''; //<-- can be removed
var maxRequests = 1000; //e.g. max 1000 requests
//create empty promise to resolve later
var resolveStart = null;
var request = new Promise(function(resolve){
resolveStart = resolve;
});
//append 1000 potential requests
for(var i = 0; i < maxRequests; i++) {
request = request.then(createRequestPromise);
}
//here you probably should differ the "done" rejection and other errors
request.catch(function(){});
//now resolve the first promise, with empty string, to start the request chain
resolveStart('');
function createRequestPromise(lastId) {
return getPhotosDataFromRequest(userName, lastId).then(function (data)
{
lastId = data[data.length - 1].id;
console.log(data);
//stop the chain by rejection
if (!data.more_available) return Promise.reject('done');
return lastId;
});
}

Can't get $q promise in Angular factory to work correctly

The factory doesn't return anything, it just writes to a json file. I can't figure out how to use $q in this case to be sure it's done writing.
This is causing me problems in a controller, because even if I use a callback, the code does not execute in the correct order..
angular.module('jsonWrite', [])
.factory('JsonWrite', function($q) {
var nw = require('nw.gui');
var fs = require('fs');
var path = require('path');
var file = "myjsonfile.json";
var filePath = path.join(nw.App.dataPath, file);
var write = {};
write.writeJson = function(x){
var deferred = $q.defer();
fs.access(filePath, fs.F_OK, function(err) {
if (!err) {
fs.readFile(filePath, 'utf8', function(err, data) {
var myObj = JSON.parse(data);
myObj = x;
fs.writeFileSync(filePath, JSON.stringify(myObj));
});
}
else {
fs.open(filePath, "w", function(err, data) {
var myObj = {};
myObj = x;
fs.writeFileSync(filePath, JSON.stringify(myObj));
});
};
$q.resolve();
});
return deferred.promise;
};
return(write);
});
This is an example of the controller function, even if there's a callback, JsonWrite is not done writing before StateChanger.changeState executes. This causes heaps of trouble. If I put a $timeout on StateChanger, everything works fine - it executes after JsonWrite is done writing.
$scope.change = function(x){
function write(callback){
JsonWrite.writeJson(x);
callback();
};
function change(){
writemystuff(function(){
StateChanger.changeState(); // <- $timeout here and it works
})
};
change();
};
If anyone can give me an idea on what can be done, I'd be very grateful
Your factory looks fine. But you need to USE your promise - this means you need to use a ".then()" syntax:
write.writeJson().then(function() {StateChanger.changeState()});
Have a look at these examples:
https://docs.angularjs.org/api/ng/service/$q
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
}, 1000);
return deferred.promise;
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
}, function(update) {
alert('Got notification: ' + update);
});

Updated scope variable not showed by view. Promises not working

I'm trying to retrieve data from my Firebase Database, and corresponding images from Firebase Storage. The problem is, my view does not want to update itself with the data.
If I try to simply fetch the data from my database, it works perfectly. Once I add functionality to fetch pictures (which takes slightly longer) it looks like my view simply looks immediately at the scope variable and does not wait for $scope.friendsinfo to update. I think I'm doing something wrong with my promises and should be using $q, but I have no idea how exactly. Can anyone tell me what the best way would be to go about this? Thanks a lot!
var friendsRef = firebase.database().ref('friendships/' + firebase.auth().currentUser.uid);
$scope.friends = $firebaseArray(friendsRef);
$scope.friendsinfo = [];
$scope.$watch('friends', function() {
var newfriends = $scope.friends;
var newfriendsinfo = [];
for(var i = 0; i < newfriends.length; i++){
var ref = firebase.database().ref('users/' + newfriends[i].$id);
var profilePicRef = firebase.storage().ref("profilepictures/" + newfriends[i].$id + "/profilepicture");
var picPromise = fetchPicture(profilePicRef);
var newfriendid = newfriends[i].$id;
var newfriendagreed = newfriends[i].agreed;
picPromise.then(function(data){
ref.once('value', function(snapshot){
newfriendsinfo.push({
id: newfriendid,
name: snapshot.val().name,
email: snapshot.val().email,
agreed: newfriendagreed,
profilepicture: data //This is the functionality that causes my view to not display the updated $scope.friendsinfo because it takes too long.
});
});
});
}
$scope.friendsinfo = newfriendsinfo;
alert($scope.friendsinfo.length);
}, true);
function fetchPicture(ref){
return ref.getDownloadURL().then(function(url) {
return url;
}).catch(function(error) {
alert("error");
});
}
I have not got your code properly but posting code which will you to guide that how to use promises with resolve approach :
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
}, 1000);
return deferred.promise;
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
}, function(update) {
alert('Got notification: ' + update);
});
If anyone ever needs the solution, here it is. Turns out the problem is mainly caused by waiting for the for loop to finish, for which each item in term waits for another function to finish. This is how I was able to solve it. It's probably not optimal, but it'll do for now :)
var friendsRef = firebase.database().ref('friendships/' + firebase.auth().currentUser.uid);
$scope.friends = $firebaseArray(friendsRef);
$scope.friendsinfo = [];
$scope.$watch('friends', function() {
var newfriends = $scope.friends;
asyncUpdateFriendsInfo(newfriends).then(function(newlist){
$scope.friendsinfo = newlist;
});
}, true);
function fetchPicture(ref){
return ref.getDownloadURL().then(function(url) {
return url;
}).catch(function(error) {
alert("error");
});
}
function asyncUpdateFriendsInfo(newfriends){
var deferred = $q.defer();
var newfriendsinfo = [];
for(var i = 0; i < newfriends.length; i++){
var ref = firebase.database().ref('users/' + newfriends[i].$id);
var profilePicRef = firebase.storage().ref("profilepictures/" + newfriends[i].$id + "/profilepicture");
var picPromise = fetchPicture(profilePicRef);
var newfriendid = newfriends[i].$id;
var newfriendagreed = newfriends[i].agreed;
picPromise.then(function(data){
ref.once('value', function(snapshot){
newfriendsinfo.push({
id: newfriendid,
name: snapshot.val().name,
email: snapshot.val().email,
agreed: newfriendagreed,
profilepicture: data
});
}).then(function(){
if (newfriendsinfo.length == newfriends.length){
deferred.resolve(newfriendsinfo);
}
});
});
}
return deferred.promise;
}

Push 2 arrays after json loop

I need to run function "testfun 2 times", for each function I will have a few of names lets say testfun(5, global_user) // output [1,2,4,4,5] and for testfun(7, global_user) // output [9,10,11]
How I can put this 2 arrays in one array after I will run 2 functions?
testfun(5, global_user);
testfun(7, global_user);
function testfun(groupId, myUser) {
var selectStr = "Title";
var itemsUrl = "https://info.com(" + groupId + ")/users" + "?" + selectStr + "&" + orderbyStr;
var executor = new SP.RequestExecutor;
executor.executeAsync(
{
url: itemsUrl,
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: loadTeamNames,
error: errorHandler
}
);
}
var arr = [];
function loadTeamNames(data){
var jsonObject = JSON.parse(data.body);
var results = jsonObject.d.results;
var hide_groups = false;
$(results).each(function(){
var name = $(this)[0].Name;
});
}
Thanks
With JS
var mergedArray = outputOne.concat(outputTwo);
With JQuery
var mergedArray = $.merge( $.merge( [], outputOne), outputTwo);
Since testFun() uses asynchronous methods you can't do anything immediately after running it twice without waiting for both instnces to complete. This is accomplished using promises
You could use $when() and return a promise from testFun(). Will need to move loadTeamNames into testFun to do it
$.when() won't complete until both of the promises are resolved
function testfun(groupId, myUser) {
var defer = $.Deferred();
var selectStr = "Title";
var itemsUrl = "https://info.com(" + groupId + ")/users" + "?" + selectStr + "&" + orderbyStr;
var executor = new SP.RequestExecutor;
executor.executeAsync(
{
url : itemsUrl,
method : "GET",
headers : {"Accept" : "application/json; odata=verbose"},
success : loadTeamNames,
error : errorHandler
}
);
function loadTeamNames(data) {
var jsonObject = JSON.parse(data.body);
var results = jsonObject.d.results;
var hide_groups = false;
$(results).each(function () {
var name = $(this)[0].Name;
});
// resolve deferred and pass data to be used in `$.when()`
defer.resolve(results);
}
return defer.promise;
}
To use
$.when(testfun(5, global_user),testfun(7, global_user)).done(function (results1, results2) {
//do what you need to with arrays results1 & results2
});
Add defer.reject() in the errorHandler
Assuming that jsonObject.d.results is an array already, you can just do:
arr.concat(results)
This will concatenate your array so far with the new result. Have that code inside of loadTeamNames and each run of testfun will concatenate the result to your current array. not really sure what you're using all those variables inside of loadTeamNames for however.
function getTeamNames(groupId, myUser) {
var defer = $.Deferred();
var selectStr = "$select=Title,LoginName";
var orderbyStr = "$orderby=Title";
var itemsUrl = "https://sites.sp.kp.org/pub/qosstgcat/_api/web/SiteGroups/getbyid(" + groupId + ")/users" + "?" + selectStr + "&" + orderbyStr;
var executor = new SP.RequestExecutor(carootUrl);
executor.executeAsync(
{
url : itemsUrl,
method : "GET",
headers : {"Accept" : "application/json; odata=verbose"},
success : loadTeamNames,
error : errorHandler
}
);
function loadTeamNames(data) {
var jsonObject = JSON.parse(data.body);
var results = jsonObject.d.results;
var hide_groups = false;
$(results).each(function(){
var login_name = $(this)[0].LoginName;
});
defer.resolve(results);
}
return defer.promise;
}
result
$.when(getTeamNames(4, global_user),getTeamNames(185, global_user)).done(function () {
console.log(results);
});

Node.js promises using Q

I have a Node app that I'm writing where I need to use promises for async calls.
I currently have a foreach loop being called from within a .then(function()) of a promise, but when I return the end result of the foreach, I get nothing.
In the foreach I can console.log the value of data and retrieve it, but not outside the for loop before the return?
var Feeds = function(){
this.reddit = new Reddit();
}
Feeds.prototype.parseRedditData = function(){
var _this = this;
this.getData(this.reddit.endpoint).then(function(data){
return _this.reddit.parseData(data, q);
});
}
Feeds.prototype.getData = function(endpoint){
var deferred = q.defer();
https.get(endpoint, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
deferred.resolve(JSON.parse(body));
});
}).on('error', function(e) {
deferred.reject(e);
});
return deferred.promise;
}
var Reddit = function(){
this.endpoint = "https://www.reddit.com/r/programming/hot.json?limit=10";
}
Reddit.prototype.parseData = function(json, q){
var dataLength = json.data.children.length,
data = [];
for(var i = 0; i <= dataLength; i++){
var post = {};
post.url = json.data.children[i].data.url;
post.title = json.data.children[i].data.title;
post.score = json.data.children[i].data.score;
data.push(post);
}
return data;
}
Feeds.prototype.parseRedditData = function(){
var _this = this;
this.getData(this.reddit.endpoint).then(function(data){
return _this.reddit.parseData(data, q);
});
}
When i see this I see a "return" in the callback of the promise... I don't know why you're doing this, but I just want to be sure:
I you want this "return" to be the returned value of the function 'parseRedditData', this won't work.
The only way to return your data here is by using a callback, or a promise, like this:
Feeds.prototype.parseRedditData = function(callack){
var _this = this;
this.getData(this.reddit.endpoint).then(function(data){
callback(_this.reddit.parseData(data, q));
});
}

Categories

Resources