Trying to get a function to output the result of a variable once all of it asynch processes have been completed. To do this I've learned that I have to use promises and so I've spent the day learning about them.
I've written my function with promises and have looked at many tutorials but I am still getting this error, not sure what I am doing wrong. It probably has to do with what I am doing with kpiDefault or how I wrote my kpiAverage function. Also I am using coffee script so it might even be a syntax issue.
here is my code for kpiAverage
kpiAverage = (period, kpiName, params) ->
result = $q.defer()
Sads.shops.getList(params).then (data) ->
shops = data.map((d) ->
new ScopeShopWithMetrics(d, $scope.organizations.current)
)
$q.all(shops.map((d) ->
d.getAverages period
)).then( ->
shopSum = 0
i = shops.length
shopSum += shops[i]["metrics"][kpiName]["value"] while i--
shopAverage = shopSum / shops.length)
.then((shopAverage) ->
result.resolve shopAverage
result.promise
)
Now here is the code that produces the error
kpiDefault = kpiAverage(period7, "visits", testParams).then((shopAverage) ->
shopAverage
)
If i do this I don't get an error but the output isn't a number, it looks like it is a promise object.
kpiDefault = kpiAverage period7, "visits", testParams
output
Object {then: function, catch: function, finally: function}
Edit:
So it looks like I'm using promises all wrong but this leaves me even more confused. I simply want the value to be returned after the asynchronous process is done, but now I am more lost than ever.
Looked through code and found out why it was giving me that error (old code that was uncommented by accident) but I am still receiving the promise Object as an output
// Here is my function in my Factory which retrieve DATA.
result.retrieveResult = function(){
var deferred = $q.defer();
return $http.get( constant.adressApi + 'result', { cache : true, params :{ 'limit' : 50 } } )
.success(function (data) { deferred.resolve(data); })
.error(function(error){ deferred.reject(); });
return deferred.promise;
};
// Here is my call to the function from my factory service.
presentationService.retrieveResult()
.then(function(result){
$scope.result = result.data;
});
This is how i use Promise easily hope this can help you.
As you can see you create a deferred(some people call it futur). you return it at the end of the function and it will represent your result. As soon as your asynchronous function return the result if everything was fine the .success callback is call and the deferred is resolve in that case you should get the result where i wrote .then( function(result) ... Otherwise the .error is call and then you retrieve the get the reason of the error.
Short Answer
I don't see anywhere in your code that you resolve or reject your deferred so i think that's why your output display :
Object {then: function, catch: function, finally: function}
Related
I am trying to use a promise to call a function getTweets.
Not using an AJAX call, but a simple promise 'call' from 1 javascript file to another.
The function works, but i keep getting 'undefined'.
I have read dozens of questions here on stackoverflow and have spent days
to understand promises, but still can't solve it.
var Twit = require('twit') // Imports the Twitter library
require('dotenv').config() // to get the environment vars into the app
// This is the function:
function getTweets (screen_name) {
let T = new Twit({ /* <twitter key and token here> */ });
T.get('statuses/user_timeline', { screen_name: screen_name, count: 3}, function (err, data, response) {
let myTweets = [];
for (let i in data) {
let text = data[i].text;
myTweets.push(text);
}
return myTweets;
})
}
module.exports.getTweets = getTweets;
And this is the promise that tries to get the tweets:
var promise = tweets.getTweets('dizid');
promise.then(
console.log(myTweets),
console.log(err))
// gives error: promise.then(
// ^
// TypeError: Cannot read property 'then' of undefined
Any help greatly appreciated.
Your problem is that you never return anything from your getTweets() function even though it needs to return a promise. The function calls T.get() and pass it a callback function. You return from this callback function but this doesn't do anything, it doesn't mean that this value gets returned from getTweets().
This is a pretty common mistake when it comes to working with asynchronous calls. What needs to be done is to make getTweets() return a promise that gets resolved when it should.
When working with asynchronous calls that don't implement the promise interface, you need to wrap this call with a new promise. Your getTweets() function should then look like this:
function getTweets (screen_name) {
let T = new Twit({ /* <twitter key and token here> */ });
return new Promise(function(resolve, reject) {
T.get('statuses/user_timeline', { screen_name: screen_name, count: 3}, function (err, data, response) {
if (err) {
reject(err); // Reject the promise since there was an error
} else {
let myTweets = [];
for (let i in data) {
let text = data[i].text;
myTweets.push(text);
}
resolve(myTweets); // Resolve the promise with the result
}
});
});
}
However, it seems the Twit API does support the promise interface, so instead of providing a callback function you can just use the promise created by T.get(). HMR's answer explains how to do this.
Another mistake you've made is with this code:
promise.then(
console.log(myTweets),
console.log(err))
The way you've written it, it reads "Run console.log(myTweets) and console.log(err), then invoke promise.then() with the result of the former as the first argument and the result of the latter as the second argument.
then() takes callback functions (which get invoked depending on the resolving/rejection of the promise) as arguments, so the code should look like this:
promise.then(
function(myTweets) {
console.log(myTweets);
},
function(err) {
console.log(err);
});
Async/await
If you're interested in taking things further, the modern approach to working with asynchronous code is async/await, which is syntactic sugar for promises that lets you write asynchronous code more similar to regular synchronous code.
A function marked as async will implicitly return a promise, but you write it as if you return a regular value. Using the await keyword inside an async function will implicitly wait for a promise to resolve and unwrap the resolved value. The main practical benefits of this is that you can use asynchronous calls in loops and handle errors with regular try-catch blocks. Your getTweets() function would look like this using async/await:
async function getTweets(screen_name) {
let T = new Twit({ /* <twitter key and token here> */ });
const data = await T.get('statuses/user_timeline', { screen_name: screen_name, count: 3});
// Let's also use map() instead of a for loop
let myTweets = data.map(function(item) { return item.text; });
return myTweets;
}
Since get seems to return a promise you don't need to use a callback. Get Tweets can look something like this:
// in getTweets
return T.get(
'statuses/user_timeline',
{ screen_name: screen_name, count: 3}
).then(
function (data) {
console.log("data:",JSON.stringify(data,undefined,2));
return data.map(item=>item.text);
}
)
// exports the function getTweets so that other modules can use it
module.exports.getTweets = getTweets;
If that didn't work please let us know what the output of the program is (update question).
You can call getTweets like so:
tweets.getTweets('dizid')
.then(
myTweets=>
console.log(myTweets),
err=>
console.log(err)
)
I think you forget add function like
promise.then(function(res){
//code
}
Your .then() should include a call back function.
promise.then( res => {
console.log(res);
});
edit: I'm using an ES6 syntax for arrow functions, in case you're new to that.
Currently it works when I use console.log(result) in my "exports.findOneProblem" while running "node jkl.js". I am able to see the result. However, when I use return instead of console.log(), all I get is a Promise {pending} in console. Please fill in the gaps .... learning how to work with promises, thanks.
//asd.js
exports.findOneProblem = function(problemId) {
return RClient.authenticate(options).then(function (client) {
const Problem = client.Problem;
return Problem.findOne(problemId)
}).then(function(result){
return result
});
};
the second file: jkl.js
var okay = require('./asd');
var moneymoney = okay.findOneProblem(263)
console.log(moneymoney)
var honeyhoney = moneymoney.then(function(result){
return result
})
console.log(honeyhoney)
When you receive a Promise, that means you're going to get a value "later" i.e. after all of your synchronous code is done running. The way to access a value provided by a Promise is using the .then function.
moneymoney.then(function(result) {
console.log(result);
// Add your code for using the result of `okay.findOneProblem(263)` here
});
I am trying to do multiple $http call and my code looks something like this:
var data = ["data1","data2","data3"..."data10"];
for(var i=0;i<data.length;i++){
$http.get("http://example.com/"+data[i]).success(function(data){
console.log("success");
}).error(function(){
console.log("error");
});
}
How can I have the promise to know all $http call is successfull? If anyone of it fail, will perform some action.
You could also use $q.all() method.
So, from your code:
var data = ["data1","data2","data3"..."data10"];
for(var i=0;i<data.length;i++){
$http.get("http://example.com/"+data[i]).success(function(data){
console.log("success");
}).error(function(){
console.log("error");
});
}
You could do:
var promises = [];
data.forEach(function(d) {
promises.push($http.get('/example.com/' + d))
});
$q.all(promises).then(function(results){
results.forEach(function(data,status,headers,config){
console.log(data,status,headers,config);
})
}),
This above basically means execute whole requests and set the behaviour when all have got completed.
On previous comment:
Using status you could get to know if any have gone wrong. Also you could set up a different config for each request if needed (maybe timeouts, for example).
If anyone of it fail, will perform some action.
From docs which are also based on A+ specs:
$q.all(successCallback, errorCallback, notifyCallback);
If you are looking to break out on the first error then you need to make your for loop synchronous like here: Angular synchronous http loop to update progress bar
var data = ["data1", "data2", "data3", "data10"];
$scope.doneLoading = false;
var promise = $q.all(null);
angular.forEach(data, function(url){
promise = promise.then(function(){
return $http.get("http://example.com/" + data[i])
.then(function (response) {
$scope.data = response.data;
})
.catch(function (response) {
$scope.error = response.status;
});
});
});
promise.then(function(){
//This is run after all of your HTTP requests are done
$scope.doneLoading = true;
});
If you want it to be asynchronous then: How to bundle Angular $http.get() calls?
app.controller("AppCtrl", function ($scope, $http, $q) {
var data = ["data1", "data2", "data3", "data10"];
$q.all([
for(var i = 0;i < data.length;i++) {
$http.get("http://example.com/" + data[i])
.then(function (response) {
$scope.data= response.data;
})
.catch(function (response) {
console.error('dataerror', response.status, response.data);
break;
})
.finally(function () {
console.log("finally finished data");
});
}
]).
then(function (results) {
/* your logic here */
});
};
This article is pretty good as well: http://chariotsolutions.com/blog/post/angularjs-corner-using-promises-q-handle-asynchronous-calls/
Accepted answer is okay, but is still a bit ugly. You have an array of things you want to send.. instead of using a for loop, why not use Array.prototype.map?
var data = ["data1","data2","data3"..."data10"];
for(var i=0;i<data.length;i++){
$http.get("http://example.com/"+data[i]).success(function(data){
console.log("success");
}).error(function(){
console.log("error");
});
}
This becomes
var data = ['data1', 'data2', 'data3', ...., 'data10']
var promises = data.map(function(datum) {
return $http.get('http://example.com/' + datum)
})
var taskCompletion = $q.all(promises)
// Usually, you would want to return taskCompletion at this point,
// but for sake of example
taskCompletion.then(function(responses) {
responses.forEach(function(response) {
console.log(response)
})
})
This uses a higher order function so you don't have to use a for loop, looks a lot easier on the eyes as well. Otherwise, it behaves the same as the other examples posted, so this is a purely aesthetical change.
One word of warning on success vs error - success and error are more like callbacks and are warnings that you don't know how a promise works / aren't using it correctly. Promises then and catch will chain and return a new promise encapsulating the chain thus far, which is very beneficial. In addition, using success and error (anywhere else other than the call site of $http) is a smell, because it means you're relying explicitly on a Angular HTTP promise rather than any A+ compliant promise.
In other words, try not to use success/error - there is rarely a reason for them and they almost always indicate a code smell because they introduce side effects.
With regards to your comment:
I have did my own very simple experiment on $q.all. But it only trigger when all request is success. If one if it fail, nothing happen.
This is because the contract of all is that it either resolves if every promise was a success, or rejects if at least one was a failure.
Unfortunately, Angular's built in $q service only has all; if you want to have rejected promises not cause the resultant promise to reject, then you will need to use allSettled, which is present in most major promise libraries (like Bluebird and the original Q by kriskowal). The other alternative is to roll your own (but I would suggest Bluebird).
I have a function which will generate the WinJS.xhr promise and return the same to the calling function. But after getting the promise, when doing a .then on it, all I'm getting is an empty array!!!
APPROACH 1:
Here is the function which is returning a promise. It's inside a WinJS.Class.define :
getFeaturedData: function () {
var featuredUrl = utils.getRequestUrl(globals.featuredTag, 1, 0);
return WinJS.xhr({ url: featuredUrl });
},
I'm calling that function in home.js and attaching a .then this way:
var promise = MyApp.Services.Movies.getFeaturedData();
promise.then(function(success) {
var data = success;
},
function (error) {
})
The result variable data is always an empty array which I can't seem to understand why.
APPROACH 2:
If I do .then in the getFeaturedData function itself then it works, surprisingly.
getFeaturedData: function () {
var featuredUrl = utils.getRequestUrl(globals.featuredTag, 1, 0);
var promise = WinJS.xhr({ url: featuredUrl });
promise.then(function (success) {
var data = success;
})
},
In this case, data seems to contain proper data returned from the server.
Can anyone explain this behavior? Why the first approach doesn't work and the second one does?
In example 1, getFeaturedData is returning a promise.
In example 2, it returns nothing (i.e. undefined).
However, the timing when "data" is set is unchanged. You're setting the "data" value in the completion handler of the XHR in both cases. Most likely the difference lies in where you had set your breakpoint. In neither case will "data" be set at the end of getFeaturedData, or at the end of the calling block of code. Instead, it will be set sometime later when the XHR promise completes.
A couple other notes:
Approach 2 can return the result of promise.then() (which is another promise) so that the caller can actually schedule work to happen once data is set.
In both cases, the "promise" variable is kind of extraneous, or it could be set to the result of either getFeaturedData().then(), or xhr().then().
I don't if it is a typo but your first approach is missing an ')' at the end of the function error to enclose the promise result.
Other than that, I can't see why the 1st approach wouldn't work.
Having said that, here's how I would write it:
getFeaturedData: function () {
return new WinJS.Promise(function (complete, fail){
var featuredUrl = utils.getRequestUrl(globals.featuredTag, 1, 0);
WinJS.xhr({ url: featuredUrl }).then(
function (data){
complete(data);
}, function (err){
fail(err);
});
});
}
MyApp.Services.Movies.getFeaturedData().then(
function (data){
//do whatever you want with the data
}, function (err){
// handle errors here
});
It is easier for me to see that getFeaturedData returns a promise. In your case it is clear since it is well known that WinJS.xhr returns a promise, but it will not always be that easy to 'see' right away.
The scope variable $scope.quizArray appears as undefined when accessed from the controller. The block of code below is definitely read as I have previously put tests in.
app.factory('getQuizService', function($http){
return {
getQuiz:function(videoId,scope){
var $promise=$http.post("http://localhost/PHP/getQuiz.php", {'videoId': videoId});
$promise.then(function(msg){
scope.quizArray = "TEST";
});
}
}
});
Controller code - The service is called by:
function getQuizList(){
getQuizService.getQuiz(videoIdService.getId(),$scope);
alert($scope.quizArray);
}
however the alert produces the result 'undefined' instead of 'TEST'
Can anyone see where I've gone wrong? Any help would be appreciated. Thanks
Is there a reason you're doing it like this?
Take a look at the approach(es) below:
Refer to: http://docs.angularjs.org/api/ng/service/$q for more information about how the promises work.
For example, the reason why the approach that lib3d has pointed out works:
then(successCallback, errorCallback, notifyCallback) – regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected.
This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback. It also notifies via the return value of the notifyCallback method. The promise can not be resolved or rejected from the notifyCallback method.
Code:
app.factory('getQuizService', function($http){
return {
getQuiz:function(videoId,scope){
return $http.post("http://localhost/PHP/getQuiz.php", {'videoId': videoId});
//If you want to handle the promise here, you can include the $q service in your controller and do this:
var deferred = $q.defer();
$http.post("...").then(function(data) {
//Special treatment here
deferred.resolve(data)
}, function(error) {
deferred.reject(error)
})
return deferred.promise
//As indicated by lib3d, you can directly do this as well:
return $http.post("...").then(function(data) {
//treatment
return data
}, function(error) {
//error handling
});
}
}).controller("Ctrl", function($scope, getQuizService) {
getQuizService.getQuiz(1).then(function(data) {
$scope.quizArray = data;
//Code when everything goes ok,
}, function(error) {
//Code when everything goes false;
})
})