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; });
}
Related
I am trying to use promises with loops and nested functions that are within some of those loops. I have a series of functions that are supposed to bring back SharePoint list items from REST calls - once those are done executing, then another function gets called that uses the data that was brought back.
Since there are multiple lists, and subsequently multiple list items in each, I used a while loop to make each REST call, and from there, the data (list items) are put into objects. Those objects gets placed into an array and that's what that second functions uses to continue on.
I'm having trouble receiving the responses from the promises. I was thinking to have multiple promises that get pushed to an array, then finally use Promise.all to see if everything resolved before using then. The problem is that all the promises stay pending since I'm not returning the resolve correctly. Please see below.
function onQuerySuccess(sender, args) {
var itemEnumerator = items.getEnumerator();
while (itemEnumerator.moveNext()) {
var promise = new Promise(function (resolve, reject) {
var item = itemEnumerator.get_current();
item = item.get_item('URL');
var itemUrl = item.get_description();
getRequestItemsFromList(itemUrl);
});
promises.push(promise); // all promises are present, but their status is pending
}
console.log(promises);
Promise.all(promises).then(function (val) {
console.log(val);
execFuncs(); // function to execute once all the above are done
}).catch(function (response) {
console.log(response);
});
}
Because there's a lot of functions involved, so this is the order of execution:
getRequestItemsFromList //gets url for each list
execCrossDomainRequest (on success call) // makes REST call to get list and its items
cleanData // trims data and puts it in objects
The last is where I figured I call Promise.resolve() since that's the end of the line.
Either way, that's not working. I checked out other threads, but I'm trying to do this without using any libraries. Thanks in advance.
Edit:
Full relevant code:
var promises = [];
window.requests = [];
function getRequestLists() {
var requestsLists = hostWeb.get_lists().getByTitle('Name'); // sharepoint list with all the request list urls.
context.load(requestsLists);
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml('<View></View>');
var items = requestsLists.getItems(camlQuery);
context.load(items, 'Include(URL)');
context.executeQueryAsync(onQuerySuccess, onQueryFail);
function onQuerySuccess(sender, args) {
var itemEnumerator = items.getEnumerator();
while (itemEnumerator.moveNext()) {
var promise = new Promise(function (resolve, reject) {
var item = itemEnumerator.get_current();
item = item.get_item('URL');
var itemUrl = item.get_description();
getRequestItemsFromList(itemUrl);
});
promises.push(promise);
}
console.log(promises);
Promise.all(promises).then(function (val) {
console.log(val);
execFuncs(); // not shown here
}).catch(function (response) {
console.log(response);
});
}
function onQueryFail(sender, args) {
alert("Request to retrieve list URL items has failed. " + args.get_message());
}
}
function getRequestItemsFromList(url) {
var lastPos = getSubstringIndex(url, "/", 4);
var webUrl = url.substring(0, lastPos); // truncates list url to parse out web url
var absListPos = getSubstringIndex(url, "AllItems.aspx", 1);
var absListUrl = url.substring(0, absListPos); // truncates the AllItems.aspx at the end of the list url
var relListPos = getSubstringIndex(absListUrl, "/", 3);
var relListUrl = absListUrl.substring(relListPos, absListUrl.length); // gets the list's relative url
var listName = "List Name";
console.log(webUrl);
execCrossDomainRequest(webUrl, listName, absListUrl);
}
function execCrossDomainRequest(webUrl, listName, absListUrl) {
var executor = new SP.RequestExecutor(appWebUrl);
executor.executeAsync({ // to collect the list description
url: appWebUrl + "/_api/SP.AppContextSite(#target)/web/lists/getbytitle(#name)?" +
"#target='" + webUrl + "'&#name='" + listName + "'" +
"&$select=Description",
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: onCallSuccess,
error: onCallFail
});
function onCallSuccess(data) {
var json = JSON.parse(data.body);
var description = json.d.Description;
executor.executeAsync({ // to collect the list item information
url: appWebUrl + "/_api/SP.AppContextSite(#target)/web/lists/getbytitle(#name)/items?" +
"#target='" + webUrl + "'&#name='" + listName + "'" +
"&$top=500&$select=*," +
"Assigned_x0020_To/Title" +
"&$expand=Assigned_x0020_To/Id",
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: onItemsCallSuccess,
error: onItemsCallFail
});
function onItemsCallSuccess(data) {
var itemsJson = JSON.parse(data.body);
var results = itemsJson.d.results;
cleanData(results, description, absListUrl);
}
function onItemsCallFail(data, errorCode, errorMessage) {
console.log("Could not make list items cross domain call. " + errorMessage);
}
}
function onCallFail(data, errorCode, errorMessage) {
console.log("Could not make list cross domain call. " + errorMessage);
}
}
function cleanData(results, listDescription, absListUrl) {
if (!results.length) {
return;
}
for (var i = 0; i < results.length; i++) {
var client = listDescription;
var id = results[i].ID;
...
}
var request = new Request(client, id, path, title, status, estimated, assignedTo, priority, description, response);
window.requests.push(request);
}
return Promise.resolve();
}
When you use a promise constructor like this:
var promise = new Promise(function (resolve, reject) {
It implies that somewhere inside this block you are calling resolve and/or reject, to settle the promise.
But you are never doing it, leaving the created promise object in pending state forever.
See Constructing a promise.
It seems to me your promises array is not defined: do var promises = []; before your while loop.
I'm having issues creating a Youtube Service which does a simple search based on a 'search term'. What I would like to do is allow controllers to call into my youtube service (i.e. searchVideo("dogs")) and get a video-id, as a string, back.
At this time, I'm noticing that the requestVideoId is being returned before the request can finish executing. Being unfamiliar with JS, I would like an example of how I can have my searchVideo() function return the requestVideoId correctly (after the request has finished).
(function () {
'use strict';
var serviceId = 'youtubeService';
angular.module('app').factory(serviceId, ['common', youtubeService]);
function youtubeService(common) {
var service = {
searchVideo: searchVideo
};
return service;
function searchVideo(searchText) {
var requestVideoId = "";
var request = gapi.client.youtube.search.list({
q: searchText,
part: 'snippet'
});
request.execute(function(response) {
var responseList = response.result;
console.log(responseList.items[0].id.videoId);
requestVideoId = responseList.items[0].id.videoId;
});
console.log(requestVideoId);
return requestVideoId;
}
}
})();
I've checked this question... YouTube Asynchronous function ... But I want to keep the searchVideo() function. I could be missing something. Thanks ahead of time.
searchVideo should return a promise that gets resolved in future. Something like:
function searchVideo(searchText) {
var defer=$q.defer();
var requestVideoId = "";
var request = gapi.client.youtube.search.list({
q: searchText,
part: 'snippet'
});
request.execute(function(response) {
var responseList = response.result;
console.log(responseList.items[0].id.videoId);
requestVideoId = responseList.items[0].id.videoId;
defer.resolve(requestVideoId);
});
console.log(requestVideoId);
return defer.promise;
}
Now you can use the promise api, to register a callback when the promise is resolved:
youtubeService.searchVideo('test')
.then(function(requestVideoId) { // can access the id here});
Learn about what promises are and how promises work in Angular.
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;
});
},
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.
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.