Need to return result after multiple promises - javascript

When I call this function it returns a result immediately instead of delaying to after the third promise. What can I do??
getBlogs: function(blogId){
var blogcomments = new Entities.BlogCommentCollection();
var blogs = new Entities.BlogCollection();
var defera = $.Deferred();
var deferb = $.Deferred();
var deferc = $.Deferred();
var model;
//alert(model);
$.get("/lightning/presentation/blogs", function(val){
defera.resolve(val);
});
var promisea = defera.promise();
$.when(promisea).done(function(val){
var models = initializeBlogs(JSON.parse(val));
blogs.reset(models);
model = blogs.at(blogId);
//alert(JSON.stringify(model));
$.get("/lightning/presentation/blogs/?blogId=" + blogId, function(full){
deferb.resolve(full);
});
});
var promiseb = deferb.promise();
$.when(promiseb).done(function(full){
model.set('full', full);
//alert(JSON.stringify(model));
$.get("/lightning/presentation/blogs/?comments=" + blogId, function(res){
deferc.resolve(res);
});
});
var promisec = deferc.promise();
$.when(promisec).done(function(res){
if(res.length === 0){
blogcomments.reset();
}else{
//alert(res)
var models = initializeBlogComments(JSON.parse(res));
blogcomments.reset(models);
model.set('comments', blogcomments)
//return model;
}
currentBlog = model;
alert(JSON.stringify(model));
//return model;
});
//alert(JSON.stringify(model));
return model;
},

Promises are async. They don't cause the current function to delay return (that would be sync). They return, well, a promise that will resolve at some point in the future.
So the most basic way to fix your code is to return not the model, but the promise
getBlogs: function(blogId) {
var deferAll = $.Deferred();
// your last set will then respond to them all
$.when(promisec).done(function(res){
if(res.length === 0){
blogcomments.reset();
}else{
//alert(res)
var models = initializeBlogComments(JSON.parse(res));
blogcomments.reset(models);
model.set('comments', blogcomments)
//return model;
}
currentBlog = model;
// THIS is where it gets resolved
deferAll.resolve(model);
});
// do whatever you need to
return deferAll.promise();
}
And then you call getBlogs as
getBlogs(25).then(function(model) {
// model is given here
});
However, there are better ways to do this. First of all, you can chain promises.
$.get("/lightning/presentation/blogs/?blogId=" + blogId).then(function(full){...}).then().then() // etc
Lastly, if you really are going to do multiple async things in an order like that, may I recommend caolan's excellent async library? It makes thinks like this much easier. https://github.com/caolan/async

getBlog: function(blogId, model){
var blogcomments = new Entities.BlogCommentCollection();
var defer = $.Deferred();
var fullBlog;
$.when(
$.get("/lightning/presentation/blogs/?blogId=" + blogId, function(full){
fullBlog = full;
}),
$.get("/lightning/presentation/blogs/?comments=" + blogId, function(res){
var models = initializeBlogComments(JSON.parse(res));
blogcomments.reset(models);
})
).done(function() {
model.set('full', fullBlog);
model.set('comments', blogcomments);
defer.resolve(model);
});
return defer.promise();
},

Improving upon the answer you already provided, you can do it this way that avoids creating the extra deferred and just uses the promise that $.when() already returns. :
getBlog: function(blogId, model){
var blogcomments = new Entities.BlogCommentCollection();
var fullBlog;
return $.when(
$.get("/lightning/presentation/blogs/?blogId=" + blogId, function(full){
fullBlog = full;
}),
$.get("/lightning/presentation/blogs/?comments=" + blogId, function(res){
var models = initializeBlogComments(JSON.parse(res));
blogcomments.reset(models);
})
).then(function() {
model.set('full', fullBlog);
model.set('comments', blogcomments);
return model;
});
},
Or, you could additionally use the return values from $.when() to avoid the separate ajax callbacks like this:
getBlog: function(blogId, model){
var blogcomments = new Entities.BlogCommentCollection();
return $.when(
$.get("/lightning/presentation/blogs/?blogId=" + blogId),
$.get("/lightning/presentation/blogs/?comments=" + blogId)
).then(function(r1, r2) {
model.set('full', r1[0]);
var models = initializeBlogComments(JSON.parse(r2[0]));
blogcomments.reset(models);
model.set('comments', blogcomments);
return model;
});
},

Related

datatables not rendering the results

I am using the datatables plugin to render some results, the complexity here is that I have to loop through some lists in Sharepoint, then make a query, and then append each result into a final result, and then show that final result.
When I debug the foreach(result), I can see that the results are appended, and I get 13 result items so far.
However when the debugger reaches the datatable.add method, then the array is empty, and nothing is rendered.
function GetData(billCycleId, clientCode, jobCodes, engagementCode) {
var enhanceFunctions = [
function(searchResultRow) {
return spService.AddHyperLinkOnFields(searchResultRow, config.HyperLinks);
},
function(searchResultRow) {
return spService.AddPresenceOnFields(searchResultRow, config.UserFields);
},
function(searchResultRow) {
return spService.FormatDateFields(searchResultRow, config.DateFields, generalConfig.DateTimeFormat);
},
function(searchResultRow) {
return spService.AddImageMapping(searchResultRow, config.ImageFields);
},
function(searchResultRow) {
return spService.FormatNumberFields(searchResultRow, config.NumberFields);
}
];
var selectProperties = spService.TransformFieldsToSelectProperties(config.Fields);
var extendedSelectProperties = selectProperties.slice(); // copy array
var hyperLinkedProperties = spService.TransformFieldsToSelectProperties(config.HyperLinks)
extendedSelectProperties = extendedSelectProperties.concat(hyperLinkedProperties);
spService.GetAllListsFromWeb()
.then(function(lists){
var listEnumerator = lists.getEnumerator();
var result =[];
while (listEnumerator.moveNext()) {
var oList = listEnumerator.get_current();
var title = oList.get_title();
var id = oList.get_id();
if(title.indexOf("Bill Cycles") !== -1){
// Get data from SP
GetRelatedBillCyclesFromList(id, extendedSelectProperties, billCycleId, clientCode, jobCodes, engagementCode, enhanceFunctions)
.then(function (data) {
var trimmedData = spService.SpSearchQuery.TrimSearchResultsToSelectProperties(data, selectProperties);
// Add data to dataTable
trimmedData.forEach(function(item){ // loop over source array
result.push(item); //append to result array
});
})
.catch (function (message) {
vm.Name = "Error";
vm.ValidDataLoaded = true;
});
}
//Do something with oList.
}
var dataTable = $(tableSelector).DataTable();
dataTable.clear().rows.add(result).columns.adjust().draw(); // Resize columns based on new data sizes
vm.ValidDataLoaded = true;
})
}
function getAllListsFromWeb(){
var deferred = $q.defer();
var context = SP.ClientContext.get_current();
var web = context.get_web();
var lists = web.get_lists();
context.load(lists);
context.executeQueryAsync(
function() {
$log.info("Successfully retrieved list item result");
deferred.resolve(lists);
},
function(error, errorInfo) {
$log.warn("Retrieving list item result failed");
deferred.reject(errorInfo);
}
);
return deferred.promise;
}
Update 1
Also tried this but didnt work
function GetData(billCycleId, clientCode, jobCodes, engagementCode) {
var enhanceFunctions = [
function(searchResultRow) {
return spService.AddHyperLinkOnFields(searchResultRow, config.HyperLinks);
},
function(searchResultRow) {
return spService.AddPresenceOnFields(searchResultRow, config.UserFields);
},
function(searchResultRow) {
return spService.FormatDateFields(searchResultRow, config.DateFields, generalConfig.DateTimeFormat);
},
function(searchResultRow) {
return spService.AddImageMapping(searchResultRow, config.ImageFields);
},
function(searchResultRow) {
return spService.FormatNumberFields(searchResultRow, config.NumberFields);
}
];
var selectProperties = spService.TransformFieldsToSelectProperties(config.Fields);
var extendedSelectProperties = selectProperties.slice(); // copy array
var hyperLinkedProperties = spService.TransformFieldsToSelectProperties(config.HyperLinks)
extendedSelectProperties = extendedSelectProperties.concat(hyperLinkedProperties);
var result =[];
var data = spService.GetAllListsFromWeb()
.then(function(lists){
var listEnumerator = lists.getEnumerator();
while (listEnumerator.moveNext()) {
var oList = listEnumerator.get_current();
var title = oList.get_title();
var id = oList.get_id();
if(title.indexOf("Bill Cycles") !== -1){
// Get data from SP
GetRelatedBillCyclesFromList(id, extendedSelectProperties, billCycleId, clientCode, jobCodes, engagementCode, enhanceFunctions)
.then(function (data) {
var trimmedData = spService.SpSearchQuery.TrimSearchResultsToSelectProperties(data, selectProperties);
// Add data to dataTable
trimmedData.forEach(function(item){ // loop over source array
result.push(item); //append to result array
});
})
.catch (function (message) {
vm.Name = "Error";
vm.ValidDataLoaded = true;
});
}
}
return result;
})
var resultadata = data;
var dataTable = $(tableSelector).DataTable();
dataTable.clear().rows.add(resultdata).columns.adjust().draw(); // Resize columns based on new data sizes
vm.ValidDataLoaded = true;
}
Since data seems to be a promise you could try this:
//not saving it to data
spService.GetAllListsFromWeb()
.then(function (lists) {
var listEnumerator = lists.getEnumerator();
return Promise.all(
(function(){
var promises = [];
while (listEnumerator.moveNext()) {
var oList = listEnumerator.get_current();
var title = oList.get_title();
var id = oList.get_id();
if (title.indexOf("Bill Cycles") !== -1) {
// Get data from SP !!! this is also async and returns a promise
// add the promise to promises array and wait for all to finish
// look above in Promise.all
promises.push(
GetRelatedBillCyclesFromList(
id,
extendedSelectProperties,
billCycleId,
clientCode,
jobCodes,
engagementCode,
enhanceFunctions
)
.then(function (data) {
return spService
.SpSearchQuery
.TrimSearchResultsToSelectProperties(
data,
selectProperties
);
})
);
}
}
return promises
})() //IIFE returning an array of promises
);
})
.then(
function(data){
console.log("got data:",JSON.stringify(data,undefined,2));
var resultadata = data;
var dataTable = $(tableSelector).DataTable();
dataTable.clear().rows.add(resultdata).columns.adjust().draw(); // Resize columns based on new data sizes
vm.ValidDataLoaded = true;
}
);
You should really check out what a promise is and how it's used in JavaScript.
Since functions you write are always synchronous (only one thread running your code) your functions need to return a value immediately (non blocking).
When a function needs to make a network request, file IO or long running process you immediately return a promise. A promise is an object that has a function called then that takes 2 handler functions
Resolve handler: This is called when the promise resolves (network request finished and value is returned). The handler is passed one argument which is the resolve value (for network request this would be the response).
Reject handler: This is called when the promise rejects. For example the url for a request is invalid or server is down. The parameter to this function is the error.
So when you try to do stuff like:
var result = [];
var later = x => new Promise(r=>setTimeout(r(x),100));
[1,2,3,4,5]
.map(
x =>
later(x)
.then(
x => {
console.log("resolved with:",x);
result.push(x);
return x;
}
)
);
console.log("first output",result);
//the output will be:
// first output []
// resolved with: 1
// resolved with: 2
// resolved with: 3
// resolved with: 4
// resolved with: 5
You will see that by the time you try to do something with result none of the promises are resolved so it's empty. In the answer I put here I use Promise.all to resolve all promises and then use the resolve values for the dataTable.

Nesting promises with $http in AngularJS 1.0.7

I´m trying to understand how promises work in Angular 1.0.7, but the syntax and the concept are difficult to me. I created a related previous post Nesting promises with $resources in AngularJS 1.0.7 that is working fine. However, when I try to do the same but replacing the $resource with an $http service, then it´s not working for me as expected.
The code never wait for the promise to come and I don´t get any result.
I attach the code:
// URL has to be parsed to get the destination, language and boatType
var parseURL = function() {
var language = $routeParams.language;
var url = $location.path();
var deferred = $q.defer();
var promise = deferred.promise;
promise.then(function success(result) {
var destination = result;
console.log("destination:" + destination);
searchBoats(destination);
});
parseDestination(url, language).then(deferred.resolve);
};
parseURL();
var parseDestination = function(url, language) {
console.log("parseDestination.begin");
var departure = UrlService.parseUrlDeparture(url);
var deferred = $q.defer(),
promise = deferred.promise;
TranslationService.getTranslatedDeparture(departure, language, API_SERVER_URL, deferred.resolve, deferred.reject);
return promise;
};
// The function in the service
getTranslatedDeparture: function(destination, language, api) {
var defered = $q.defer();
var destinationPromise = defered.promise;
$http.get("http://" + api + "/translatedDepartures?departure=" + destination + ";lang=" + language + ";matchStart=" + true).then(
//var destination = result.data.map(function (source) { return source.element_translation; });
defered.resolve
);
return destinationPromise;
}
You are using promises wrong in just about every way imaginable. Promises are intended to be chained and create new promises using .then(). And doing this will fix your bugs and cut the length of your code in half. As long as you have a promise to start with (which you do because $http.get returns a promise), you don't need, and shouldn't use, $q.defer():
// URL has to be parsed to get the destination, language and boatType
var parseURL = function() {
var language = $routeParams.language;
var url = $location.path();
parseDestination(url, language)
.then(function (result) {
var destination = result;
console.log("destination:", destination);
searchBoats(destination);
});
};
parseURL();
var parseDestination = function(url, language) {
console.log("parseDestination.begin");
var departure = UrlService.parseUrlDeparture(url);
return TranslationService.getTranslatedDeparture(departure, language, API_SERVER_URL);
};
// The function in the service
getTranslatedDeparture: function(destination, language, api) {
var url = "http://" + api + "/translatedDepartures?departure=" + destination + ";lang=" + language + ";matchStart=" + true;
return $http.get(url)
.then(function (result) { return result.data; });
}

Promises in functions, angularjs

How to implement promise into function so i can get rid off timeout, if it is even possible? I'm getting some data from factory 'Prim',function look's like :
$scope.getPre = function(id){
var url = WEB_API.MainUrl + '/api/prim/' + id +'/' + $window.sessionStorage.getItem('idsom');
Prim.getprim(function(data) {
$scope.prim1 = data;
$scope.prim = $scope.prim1[0];
}, url);
$scope.$apply();
}
and the timeout i want to rid off :
setTimeout(function() { // it can't work without timeout
$scope.getPre($routeParams.idprim);
}, 100);
Your function should return a promise, so you could use .then() on it:
$scope.getPre = function(id){
var deferred = $q.defer()
var url = WEB_API.MainUrl + '/api/prim/' + id +'/' + $window.sessionStorage.getItem('idsom');
Prim.getprim(function(data)
$scope.prim1 = data;
$scope.prim = $scope.prim1[0];
deferred.resolve();
}, url);
return deferred.promise;
}
And then just use it like this:
$scope.getPre($routeParams.idprim).then(function () {
// Do whatever you did after setTimeout
});

Looping with promises in node returns same response

When using q I get the same response the amount of times it loops:
function start() {
var the_promises = [];
var api_info = config.AFV
var deferred = Q.defer();
var extPath = '/search/'
var callType = 'GET'
var mymd = buildmd5(api_info, extPath, callType);
for(var page=1;page<4;page++) {
console.log('getting page:'+page)
new Client().get(url'+page, function(data, response){
deferred.resolve(data);
});
the_promises.push(deferred.promise);
}
return Q.all(the_promises);
}
start().then(function (clips) {
inspect(clips)
});
Output:
resultPageNumber: [ '1' ],
resultPageNumber: [ '1' ],
resultPageNumber: [ '1' ],
resultPageNumber: [ '1' ],
I feel like I might have the var deferred = Q.defer(); in the wrong place and it snot saving to different promises. I know this version of the code isnt functional but I'm only concerned about the promises. Thank you!
You need to create a new deferred for every new Client. And toss that loop body in a immediately invoked function to properly scope the var.
function start() {
var the_promises = [];
var api_info = config.AFV;
var extPath = '/search/';
var callType = 'GET';
var mymd = buildmd5(api_info, extPath, callType);
for(var page=1;page<4;page++) {
(function() {
var deferred = Q.defer();
console.log('getting page:'+page);
new Client().get('url'+page, function(data, response){
deferred.resolve(data);
});
the_promises.push(deferred.promise);
}());
}
return Q.all(the_promises);
}
start().then(function (clips) {
inspect(clips);
});
I don't like the existing solution, it's risky. What happens if one promise errors? What happens if you have another issue in your code?
It is my advice to always promisify the lowest primitive possible.
In our case. I would promisify the Clients .get method.
Client.prototype.getAsync = function(url){
var d = Q.defer(); // or better, using Q.Promise(
// please also consider adding a d.reject on the error case.
this.get(url, function(data){ d.resolve(data); });
return d.promise;
}
This new api would let you do:
var promises = [1,2,3,4].map(function(page){
return new Client().getAsync("url"+page);
});
return Q.all(promises);
And then get a similar API to what Dan's answer has. This is a lot nicer in my opinion, and a lot less error prone.

Service function returning empty object

I've got a factory function that won't return a variable I'm trying to set in my controller. I don't get an error though, just the variable won't get set to what it's suppose to.
spApp.factory('SiteService', function ($q){
var rootUrl = window.location.protocol + "//" + window.location.hostname;
var siteMap;
//returns object containing info about all sites within collection
var getSiteMap = function () {
siteMap = {};
var promise = $().SPServices({
operation: "GetAllSubWebCollection",
async: true
});
promise.then(
function (response){
map = {}; //init the map
var web = $(response).find("Web").map(function () {
return $(this).attr('Url');
});
var webTitle = $(response).find("Web").map(function () {
return $(this).attr('Title');
});
// create map
for (var i = 0; i < web.length; i++) {
var item = web[i],
title = webTitle[i],
parts = item.split('/'),
domain = parts.splice(0, 3).join('/'),
current;
if (!map[domain]) map[domain] = {url:domain, title:title ,children:{}};
current = map[domain].children;
for (var index in parts) {
var part = parts[index];
if (!current[part]) {
current[part] = {url:domain+'/'+parts.slice(0,index+1).join('/'), title:title, children:{}};
}
current = current[part].children;
}
}
siteMap = map;
}, function(reason){
alert('FAILED:' + reason);
})
console.log(siteMap);
return siteMap;
}
return{
getSiteMap:getSiteMap
}
});
Try chaining your promises like this:
var getSiteMap = function () {
siteMap = {};
var promise = $().SPServices({
operation: "GetAllSubWebCollection",
async: true
});
return promise.then(function(response){ //return your promise
// all you code
siteMap = map;
return siteMap; //return a value to another .then in the chain
});
}
Use it like this:
SiteService.getSiteMap().then(function(siteMap){
});
The issue you have is that you are working with promises. When you put your console.log outside your then() function, you are logging the variable before it has actually been resolved.
If you put your console.log inside your then() function (after sitemap is assigned), it should show the correct value, but you still won't be able to access it reliably.
I think the simplest way for you to access the siteMap value after it has been populated with data is to pass in a callback function. Eg:
var getSiteMap = function (_callback) {
siteMap = {};
$().SPServices({
operation: "GetAllSubWebCollection",
async: true
}).then(function(response){
// Process the data and set siteMap
// ...
siteMap = map;
// now pass siteMap to the callback
_callback(siteMap);
});
You would then use this in your controller like so:
SiteService.getSiteMap(function(sitemap){
// Do something with your sitemap here
});
Now while this will work, it is just one quick example, and not necessarily the best way. If you don't like callbacks, you could create a second promise that resolves only when siteMap is assigned. Also depending on your use case for getSiteMap(), you may want to cache the value, otherwise the request will be called every time.

Categories