I have an array of links, which I get in first request. My goal is to go to every link to gather data. So I want to make a promise for every request, push them all into an array and then pass to Q.all to resolve all the promises. The problem is I can't return promise and go to the next link
Here is the function, where I tried to make multiple requests and gather data
function arrayPromise(linksArr){
function collectingData(elem){
var deferredNew = Q.defer();
var url = elem;
request(url, function(error,response,html){
if(error){
deferredNew.reject(error);
}
var $ = cheerio.load(html);
var title, content;
$('.entry-title').filter(function(){
var data = $(this);
var title = data.text();
items.text.push(
{ titleof: title }
)
})
$('.entry-content ').filter(function(){
var data = $(this);
var content = data.html();
items.text.push(
{ contentof: content})
})
deferredNew.resolve(items);
})
console.log("Returning the promise");
return defferedNew.promise;
}
var promiseArr;
console.log("LENGTH:");
console.log(linksArr.length);
for (var i = 0; i < linksArr.length; i++) {
console.log(linksArr[i]);
var tempPromise = collectingData(linksArr[i]);
console.log(tempPromise);
promiseArr.push(tempPromise);
};
return promiseArr;
}
And how I try to use it
var linksPromise = fetchLinks();
linksPromise.then(function(arr){
console.log("LINKS PROMISE RESOLVED");
Q.all(arrayPromise(arr)).then(function(data){
console.log("SUCCESS RESOLVING ALL PROMISES")
console.log(data);
},function(err){
console.log("ERROR RESOLVING ALL PROMISES", err);
});
},function(err){
console.log(err);
})
promiseArr should be declared as an array:
var promiseArr = [];
If that doesn't fix it, please provide the error that you might be seeing.
There are MULTIPLE problems
First is in
deferredNew.resolve(items);
items is defined in a local scopes not defined anywhere in scope where deferredNew.resolve(items); evaluated.
Another: Assigning empty array to promiseArr would help too.
One more: request(url, function(error,response,html) is not assigning result anywhere and your function has no return statement where you think you return promice deferredNew.resolve(items);
PS
There are more erros, check that all your function return value, for example $('..').filter(...) does not reurn values
Related
I'm trying to wrap a third party library to return an object that resolves into an object that can be displayed in the view, similar to how $resource() works. I'm aware that I can manually do .then() on the promise and then set the value, but I wanted the result to seamlessly return similar to how I can do:
this.Value = $resource("/someresource").get();
How would I change the below SomeThirdPartyFunction() to return an object that resolves in the view.
Here's an example of what I'm trying to do:
angular.module('testApp', []).controller('TestController', function ($timeout, $q) {
var TestController = this;
var SomeThirdPartyFunction = function () {
var Deferred = $q.defer();
var Promise = Deferred.promise;
$timeout(function () {
Deferred.resolve("abcd");
}, 3000);
return Promise;
};
TestController.Value = SomeThirdPartyFunction();
/* I don't want to do this:
SomeThirdPartyFunction().then(function(Value) {
TestController.Value = Value;
});*/
});
And here's a plunker: https://plnkr.co/edit/HypQMkaqXmFZkvYZXFXf?p=preview
Every example I've seen using promises just wraps $http calls, but I haven't seen any examples of calling third party libraries that return promises that resolve into objects.
From the AngularJS document:
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
So instead of return a promise, you can do something like this:
var SomeThirdPartyFunction = function() {
var getAComplextObject = function() {
return {
number: 42,
method: function() {
return this.number + 1;
}
};
};
var returnValue = {};
$timeout(function() {
console.log("Resolved!");
Object.assign(returnValue, getAComplextObject());
}, 1000);
return returnValue;
};
You can wrap it in a promise and make the promise part of the return value, doing that you can make it thenable (aka a Promise)
Under-the-hood, $resource uses angular.copy:
function myResourceGet(params) {
var emptyObj = {};
emptyObj.$resolved = false;
emptyObj.$promise = $http.get(url, {params:params})
.then(function(response) {
angular.copy(response.data, emptyObj);
}).finally(function() {
emptyObj.$resolved = true;
});
return emptyObj;
}
From the Docs:
angular.copy Overview
Creates a deep copy of source, which should be an object or an array.
If a destination is provided, all of its elements (for arrays) or properties (for objects) are deleted and then all elements/properties from the source are copied to it.
The $resource service only works when the data is an object or an array. It does not work with primitives such as a string or number.
$scope.widgettype = ' ';
$scope.getwidgettype().then(function (data) {
$scope.widgettype = data;
$scope.showdimaxis = data === 'bubble';
});
console.log($scope.widgettype);
As new to angularjs, I am just stucked over here.
Console.log is returning blank value (not the updated one).
How to access the value of $scope.widgettype (updated value) in some other function? Is there any other method to achieve this?
Your help realy appreciated!
Code on angularjs are asynchronous. So
$scope.getwidgettype().then(function (data) {
$scope.widgettype = data;
$scope.showdimaxis = data === 'bubble';
});
take some milliseconds to be performed.
Try to shift console.log inside the then clause.
$scope.widgettype = ' ';
$scope.getwidgettype().then(function (data) {
$scope.widgettype = data;
$scope.showdimaxis = data === 'bubble';
console.log($scope.widgettype);
});
To use widgettype outside of your current function, you need to return a Promise (which $http already does) and resolve it every time you need to retrieve the value.
For example:
$scope.widgettype = ' ';
var promise = $scope.getwidgettype().then(function (data) {
$scope.widgettype = data.data; // `.then` wraps it in an object, so you need `.data`
$scope.showdimaxis = data.data === 'bubble';
return data.data; // return the value within a Promise
});
And use it elsewhere as:
promise.then(function(data){ // resolving it every time we need `data`
console.log(data); // we returned `data.data`, so this time `.data` is not needed
})
It's best to write a Service/Factory for this, which might return it like this:
return this.getwidgettype().then(function(response) { // return a promise
return response.data; // and return your data
});
What am i trying to achieve is as such:
Invoking my service to retrieve all appointments in appointment types (number of types not fixed) tied to a doctor
If there are 3 appointment types, then there will be 3 async calls made
return a single promise with $q.all() after all 3 promises have been resolved
appointmentService
this.getAllDoctorAppointments = function (doctor, appointmentTypeArray) {
var promises = [];
angular.forEach(appointmentTypeArray, function (appointmentType) {
var defer = $q.defer();
$http.get('/appointments/?doctorName=' + doctor + '&apptType=' + appointmentType)
.success(function (listOfAppointments) {
defer.resolve(listOfAppointments);
promises.push(defer.promise);
});
});
return $q.all(promises);
};
In my console log, the appointmentType returns [ ].
This happens because the empty 'promises' array is returned even before the 3 async calls are made. I am still very new to the concept of promises, what is the best approach to work this out? Thanks!
$scope.getAllDoctorAppointments = function (doctor, appointmentTypeArray) {
appointmentService.getAllDoctorAppointments(doctor, appointmentTypeArray)
.then(function (appointmentType) {
//could take in any number. hardcoded 3 just for testing.
console.log(appointmentType)
angular.forEach(appointmentType[0], function (xRay) {
$scope.xRayAppts.events.push(xRay);
});
angular.forEach(appointmentType[1], function (ctScan) {
$scope.ctScanAppts.events.push(ctScan);
});
angular.forEach(appointmentType[2], function (mri) {
$scope.mriAppts.events.push(mri);
});
});
};
this.getAllDoctorAppointments = function (doctor, appointmentTypeArray) {
var promises = [];
angular.forEach(appointmentTypeArray, function (appointmentType) {
promises.push($http.get('/appointments/?doctorName=' + doctor + '&apptType=' + appointmentType)
.success(function (listOfAppointments) {
return listOfAppointments;
});
);
});
return $q.all(promises);
};
$http.get returns the promises that you wants to collect, there is no need for a new defer in this case.
the promise is not being added to the array because the code that adds it to the array, promises.push(defer.promise);, is inside of the result code of the thing you are trying to defer. So the promise wouldn't get added to the list of promises to execute until after it executed!
so you can either move that push line outside of the success call looking something like this:
angular.forEach(appointmentTypeArray, function (appointmentType) {
var defer = $q.defer();
$http.get('/appointments/?doctorName=' + doctor + '&apptType=' + appointmentType)
.success(function (listOfAppointments) {
defer.resolve(listOfAppointments);
});
promises.push(defer.promise);
});
or, you can do as lcycool suggests and just add the $http.get(...).success(...) calls to the array directly.
I am struggling with this for a while, and can't figure it out. What I have is main controller, factory, and service, and I'm trying to store array from service to $scope in controller. On view on item click this function in controller is triggered:
$scope.getSongsInPlaylist = function()
{
var playlistId = this.item.idPlayliste;
$scope.Mp3Files = songInPlaylistFactory.getSongsForPlaylist(playlistId);
}
That works ok, this function retrieves item from view and sends id of that item to function in service.
Than in my service I have code like this:
var Songs = [ ];
this.getSongsForPlaylist = function (id)
{
for (var i = 0; i < SongIsOnPlaylist.length; i++)
{
if(SongIsOnPlaylist[i].idPlayliste == id)
{
dataFactory.getDataById(mp3fileUrl, SongIsOnPlaylist[i].idPjesme)
.success(function (data) {
Songs.push(data);
alert(Songs[0].naslovPjesme);//Alert one
});
}
}
alert(Songs[0]);// Alert two
return Songs;
}
dataFactory is my factory that communicates with api in backend, and that works too. var Songs is defined as: var Songs = [ ]; And SongIsOnPlaylist is filled with data.
When I trigger this, alert two gives me undefined, and alert one gives me name of first song in Songs. That means var Songs is filled with data, but when I want it to return to controller its empty ...
Am I doing something wrong here, I would appreciate any help?
First it looks like your dataFactory.getDataById is async call.
Because of this what is actually happening is you returns an empty Songs array before it gets populated when all your async calls returns.
To solve this I would advise to use a Promise library like bluebird to do something like this:
// note that now your service will return a promise
this.getSongsForPlaylist = function (id) {
return new Promise(function(resolve, reject) {
var promises = [];
// here in your loop just populate an array with your promises
for (var i = 0; i < SongIsOnPlaylist.length; i++){
if(SongIsOnPlaylist[i].idPlayliste == id){
promises.push( dataFactory.getDataById(mp3fileUrl, SongIsOnPlaylist[i].idPjesme) )
}
}
// now use the library to resolve all promises
Promise.all(promises).then( function (results) {
//HERE YOU WILL GET the results off all your async calls
// parse the results
// prepare the array with songs
// and call
resolve(Songs);
});
});
}
Then you will use the service like this:
$scope.getSongsInPlaylist = function() {
var playlistId = this.item.idPlayliste;
songInPlaylistFactory.getSongsForPlaylist(playlistId)
.then(function(Songs){
$scope.Mp3Files = Songs
})
.error(function(err){
//here handle any error
});
}
Im trying to iterate over an array that I construct from multiple http calls inside a angular.forEach()
the function
$scope.ticket_stats = function(){
//cleaning variables
$scope.data_set = [];
$scope.closed_tickets = [];
//fetching time stamps (epoch)
$scope.time_frame = time_period.days(7);
//calling data using time stamps
angular.forEach($scope.time_frame, function(item) {
//debug
console.log(item);
var promise = tickets.status("closed", item);
promise.success(function(data){
console.log(data);
$scope.closed_tickets.push(data[0].datapoints[0][0]); // returns a numerical value
});
});
//SEE MESSAGE BELOW
$scope.data_set.push($scope.closed_tickets);
}
the last line $scope.data_set.push() is working but increment itself over time once calls return data. I would like this line to be executed once everything within the for Each loop is all done. I need to iterate over the $scope.closed_tickets array afteward to play (addition) data inside it and build up a second array.
here are the services used in this function:
// CALL TICKETS STATS
app.service('tickets', function($http){
this.status = function(status, date){
var one_snap = date - 100;
var url = "/url/render?format=json&target=sum(stats.tickets."+status+")&from="+one_snap+"&until="+date+"";
return $http.get(url);
};
});
// TIME STAMPS MATHS
app.service('time_period', function(){
var currentDate = parseInt((new Date).getTime()/1000);
this.days = function(number){
var pending = [];
for (var i = number; i > 0; i--) {
pending.push(currentDate - (87677*i));
}
return pending;
};
});
I search for information and found out about the $q.all() service but didn't manage to make this work the way I want.
Any advices would be welcomed!
Thanks!
You can use $q.all to wait for multiple ansynchronous events (promises) to finish.
$scope.ticket_stats = function() {
// list of all promises
var promises = [];
//cleaning variables
$scope.data_set = [];
$scope.closed_tickets = [];
//fetching time stamps (epoch)
$scope.time_frame = time_period.days(7);
//calling data using time stamps
angular.forEach($scope.time_frame, function(item) {
// create a $q deferred promise
var deferred = $q.defer();
//debug
console.log(item);
tickets.status("closed", item).success(function(data) {
console.log(data);
$scope.closed_tickets.push(data[0].datapoints[0][0]);
// promise successfully resolved
deferred.resolve(data);
});
// add to the list of promises
promises.push(deferred.promise);
});
// execute all the promises and do something with the results
$q.all(promises).then(
// success
// results: an array of data objects from each deferred.resolve(data) call
function(results) {
$scope.data_set.push($scope.closed_tickets);
},
// error
function(response) {
}
);
}
First, deferred represents a piece of code that will take an unknown amount of time to execute (asynchronous). deferred.resolve(data) simply states that the code is finished. Data could be anything, an object, string, whatever, but it is usually the results of your asynchronous code. Likewise you can reject a promise with deferred.reject(data) (maybe an error was thrown by the sever). Again, data can be anything but here it should probably be the error response.
deferred.promise just returns a promise object. The promise object allows you to set callbacks like .then(successFunction, errorFunction) so you know a piece of code has finished executing before moving on to successFunction (or errorFunction in the case of a failure). In our case $q has the .all method which waits for an array of promises to finish then gives you the results of all the promises as an array.
Don't forget to inject the $q service.
Try to make an array of promises only, without resolving them yet. Then aggregate them with $q.all(). After aggregated promise resolve iterate through the array of those promises again. Now you are sure that they are all resolved.
var promises = [];
angular.forEach($scope.time_frame, function(item) {
promises.push(tickets.status("closed", item));
});
var aggregatedPromise = $q.all(promises);
aggregatedPromise.success(function(){
angular.forEach(promises, function(promise) {
promise.success(function(data){
$scope.closed_tickets.push(data[0].datapoints[0][0]); // returns a numerical value
});
});
});
Maybe this is not the most efficient way to do this, but I think that should solve your problem.
Even though you mention $q.all didn't work for you, as I don't see why it should't, here's how I would do this.
Basically you want to map an array of things (time stamps) to some other things (promises in this case since we have async calls) and do some action on the resulting array.
var promises = $scope.time_frame.map(function (item) {
return tickets.status("closed", item);
});
Now we use $q.all to wait for all promises to resolve:
$q.all(promises).then(function (tickets) {
$scope.data_set.push(tickets);
});