Still trying to wrap my head around promises and how they work. Im querying the Google webmaster API to return Search Analytics data. I've set up a promise which returns the data if i call it once however i need to call it again based on the result of the previous.
For example:
startRow = 0;
data = [];
Query(startRow).then((results) => {
if (results != null) {
data.push(results)
startRow++;
// RUN SAME QUERY AGAIN
};
});
startRow needs to increase by 1 then call the same promise (with the updated startRow) if the promise returned data. Is this possible or am i looking at this totally the wrong way?
You can't call the same Promise more than once, only create new ones.
startRow = 0;
data = [];
function startQuery() {
// Generally a good idea to always return Promises,
// so you can chain them if needed
return Query(startRow).then(processResults);
}
function processResults(results) {
if (results == null) return;
data.push(results);
startRow++;
return startQuery();
};
startQuery();
Or, in a more compact way:
startRow = 0;
data = [];
function startQuery() {
return Query(startRow).then((results) => {
if (results == null) return;
data.push(results);
startRow++;
return startQuery();
});
}
startQuery();
What you can do is create a function that recursively returns all of the results from a certain page onward, then call that with an initial page value of 0:
function queryPaged(pageNum, soFar) {
return Query(pageNum).then(function (results) {
return results
? queryPaged(pageNum + 1, soFar.concat(results))
: soFar;
});
}
queryPaged(0, []).then(function (allResults) {
console.log(allResults);
});
Related
I am trying to fill an array with records from a mongoDB database using mongoose. When I am trying to fill the records. It shows an empty array outside the function even though I am declaring the outside the function. Below is the code.
var saveMessageToVariable = function(){
var records = [];
var spark_ids = [];
var obj = new Object();
Message.find().distinct("spark_id").exec(function(err,data) {
data.forEach(function (id) {
if(id != null)
spark_ids.push(id);
});
// console.log(spark_ids.length);
spark_ids.forEach(function(spark_id){
Message.findOne({"spark_id":spark_id}).sort({"date":-1}).exec(function(err,data){
obj.spark_id = data.spark_id;
obj.meesage = data.message;
obj.date = data.date;
obj.message_id = data._id;
records.push(obj);
});
});
});
console.log(records);
}
When I run this, the log is showing an empty array. How do I resolve this issue?
It's an asynchronous call and as soon as data is fetched from database control shifts to next line and therefore prints the initial value, I would prefer you to use a callback like this:
function(spark_id,callback){
Message.findOne({"spark_id":spark_id}).sort({"date":-1}).exec(function(err,data){
obj.spark_id = data.spark_id;
obj.meesage = data.message;
obj.date = data.date;
obj.message_id = data._id;
callback(obj);
});
}
function(obj)
{
records.push(obj);
}
You two other approachs for this:
1) use try and catch block.
2) use async and await keyword.
Cheers!
I don't have much experience with moongoose, but according to the docs it supports promises since Version 4.
Then this should work:
//I assume you'll need this more often
function notNull(value){ return value != null; }
//returns a promise of the records-Array
var saveMessageToVariable = function(){
//returns a promise of a formated message
function getMessage( spark_id ){
return Message.findOne({ spark_id })
.sort({ date: -1 })
//.exec()
.then( formatMessage )
}
function formatMessage( msg ){
return {
spark_id: msg.spark_id,
message: msg.message,
date: msg.date,
message_id: msg._id
}
}
return Message.find()
.distinct("spark_id")
//.exec()
.then(function( ids ){
//waits for all findOnes to complete, then returns an Array
return Promise.all(
ids.filter( notNull ).map( getMessage )
));
}
I'm not sure, wether you need exec() in this code or not. You should check that.
//usage
saveMessageToVariable.then(function(records){
console.log(records);
})
btw. saveMessageToVariable doesn't reflect at all what this function does. You should choose a better name.
I am implementing this scenario where I have to fetch data from multiple URLs iteratively and process it with some business logic and display on screen. I am implementing this in the controller as it is a requirement. All is well until part-1 and I am getting the 6 promise objects in the promises array. But, I am not getting the data into metricData. I am seeing a null in the console while running in the browser. I am sure that the data is coming in the URL response. I feel I am doing something silly in the $q.all method. Is this correct?
var calculateMutationsInDepth = function(){
//Part-1
var promises=[];
var metricData=[];
for(var depth=0 ; depth<6 ; depth++){
var resourceUrl = urlService(depth);
promises.push($http.get(resourceUrl)
.then(function(response){
return response.data;
},function(status){
return status;
}));
}
//Part-2 Resolving the promise array below
$q.all(promises).then(function(data){
for(var eachResult=0; eachResult < data.length; eachResult++){
if(null != data[eachResult]){
var eachDataObject = data[eachResult];
//For debugging console.log(eachDataObject);
for(var objCount=0; objCount < eachDataObject.length; objCount++){
if(eachDataObject[objCount].scope === "PRJ" || eachDataObject[objCount].scope === "FIL")
metricData.push(eachDataObject[objCount]);
}
}
}
});
if(metricData != null){
analyzeMutationData(metricData); //Calling a function with the aggregated data array where business logic is present
}
};
calculateMutationsInDepth(); //Calling the above function
Yes, something silly.
As written, analyzeMutationData(metricData) is called synchronously whereas metricData is populated asynchronously inside the $q.all(promises).then() callback.
Also, as written the error handler function(status){ return status; } is inappropriate. Either :
omit the error handler entirely and allow any single $http error to prevent further processing in Part 2, or
return null, allowing processing in Part 2, and the if(dataObject != null) test in part 2 to filter out any such error.
Here's the revised code with a few other tidies and a demonstration of what can be done if calculateMutationsInDepth() returns a promise.
var calculateMutationsInDepth = function() {
//Part-1
var depth, promises = [];
for(depth=0; depth<6; depth++) {
promises.push($http.get(urlService(depth))
.then(function(response) {
return response.data;
}, function(error) {
return null; // error recovery - `dataObject` below will be null
}));
}
//Part-2 Aggregate the promises, extract metric data and apply business logic
return $q.all(promises).then(function(data) { // note `return` here
var dataObject, i, j, metricData = [];
for(i=0; i<data.length; i++) {
dataObject = data[i];
if(dataObject != null) {
for(j=0; j<dataObject.length; j++) {
if(dataObject[j].scope === "PRJ" || dataObject[j].scope === "FIL") {
metricData.push(dataObject[j]);
}
}
}
}
// Analyse here, inside the .then()
if(metricData.length > 0) { // metricData is an array and will never be null, therefore test metricData.length.
return analyzeMutationData(metricData);
}
return null;
});
};
calculateMutationsInDepth().then(function(analysis) {
// all complete
// `analysis` is either null or whatever `analyzeMutationData(metricData)` returned.
}).catch(function(error) {
console.log(error);
});
Hope this helps you out! Let me know if it doesn't.
I've some problem with a library calling a function on each item. I've to check the state for this item via an ajax request and don't want to call one request per item, but get a range of item states.
Because these items are dates I can get some range pretty easy - that's the good part :)
So to to give some code ...
var itemStates = {};
var libraryObj = {
itemCallback: function(item) {
return checkState(item);
}
}
function checkState(item) {
if(!itemStates.hasOwnProperty(item)) {
$.get('...', function(result) {
$.extend(true, itemStates, result);
});
}
return itemStates[item];
}
The library is now calling library.itemCallback() on each item, but I want to wait for the request made in checkState() before calling checkState() again (because the chance is extremly high the next items' state was allready requested within the previous request.
I read about the defer and wait(), then() and so on, but couldn't really get an idea how to implement this.
Many thanks to everybody who could help me with this :)
You can achieve this by using jQuery.Deferred or Javascript Promise. In the following code, itemCallback() will wait for previous calls to finish before calling checkState().
var queue = [];
var itemStates = {};
var libraryObj = {
itemCallback: function(item) {
var def = $.Deferred();
$.when.apply(null, queue)
.then(function() {
return checkState(item);
})
.then(function(result) {
def.resolve(result);
});
queue.push(def.promise());
return def.promise();
}
}
function checkState(item) {
var def = $.Deferred();
if (!itemStates.hasOwnProperty(item)) {
$.get('...', function(result) {
$.extend(true, itemStates, result);
def.resolve(itemStates[item]);
});
} else
def.resolve(itemStates[item]);
return def.promise();
}
//these will execute in order, waiting for the previous call
libraryObj.itemCallback(1).done(function(r) { console.log(r); });
libraryObj.itemCallback(2).done(function(r) { console.log(r); });
libraryObj.itemCallback(3).done(function(r) { console.log(r); });
libraryObj.itemCallback(4).done(function(r) { console.log(r); });
libraryObj.itemCallback(5).done(function(r) { console.log(r); });
Same example built with Javascript Promises
var queue = [];
var itemStates = {};
var libraryObj = {
itemCallback: function(item) {
var promise = new Promise(resolve => {
Promise.all(queue)
.then(() => checkState(item))
.then((result) => resolve(result));
});
queue.push(promise);
return promise;
}
}
function checkState(item) {
return new Promise(resolve => {
if (item in itemStates)
resolve(itemStates[item]);
else {
$.get('...', function(result) {
$.extend(true, itemStates, result);
resolve(itemStates[item]);
});
}
});
}
//these will execute in order, waiting for the previous call
libraryObj.itemCallback(1).then(function(r) { console.log(r); });
libraryObj.itemCallback(2).then(function(r) { console.log(r); });
libraryObj.itemCallback(3).then(function(r) { console.log(r); });
libraryObj.itemCallback(4).then(function(r) { console.log(r); });
libraryObj.itemCallback(5).then(function(r) { console.log(r); });
The library is now calling library.itemCallback() on each item, but I want to wait for the request made in checkState() before calling checkState() again (because the chance is extremely high the next items' state was already requested within the previous request.
One thing I can think of doing is making some caching function, depending on the last time the function was called return the previous value or make a new request
var cached = function(self, cachingTime, fn){
var paramMap = {};
return function( ) {
var arr = Array.prototype.slice.call(arguments);
var parameters = JSON.stringify(arr);
var returning;
if(!paramMap[parameters]){
returning = fn.apply(self,arr);
paramMap[parameters]={timeCalled: new Date(), value:returning};
} else {
var diffMs = Math.abs(paramMap[parameters].timeCalled - new Date());
var diffMins = ( diffMs / 1000 ) / 60;
if(diffMins > cachingTime){
returning = fn.apply(self,arr);
paramMap[parameters] = {timeCalled: new Date(), value:returning};
} else {
returning = paramMap[parameters].value;
}
}
return returning;
}
}
Then you'd wrap the ajax call into the function you've made
var fn = cached(null, 1 , function(item){
return $.get('...', function(result) {
$.extend(true, itemStates, result);
});
});
Executing the new function would get you the last promise called for those parameters within the last request made at the last minute with those parameters or make a new request
simplest and dirty way of taking control over the library is to override their methods
But I don't really know core problem here so other hints are below
If you have the control over the checkState then just collect your data and change your controller on the server side to work with arrays that's it
and if you don't know when the next checkState will be called to count your collection and make the request use setTimeout to check collection after some time or setIterval to check it continuously
if you don't want to get same item multiple times then store your checked items in some variable like alreadyChecked and before making request search for this item in alreadyChecked
to be notified when some library is using your item use getter,
and then collect your items.
When you will have enough items collected then you can make the request,
but when you will not have enought items then use setTimeout and wait for some time. If nothing changes, then it means that library finishes the iteration for now and you can make the request with items that left of.
let collection=[];// collection for request
let _items={};// real items for you when you don't want to perfrom actions while getting values
let itemStates={};// items for library
let timeoutId;
//instead of itemStates[someState]=someValue; use
function setItem(someState,someValue){
Object.defineProperty(itemStates, someState, { get: function () {
if(typeof timeoutId=="number")clearTimeout(timeoutId);
//here you can add someState to the collection for request
collection.push(_items[someState]);
if(collection.length>=10){
makeRequest();
}else{
timeoutId=setTimeout(()=>{...checkCollectionAndMakeRequest...},someTime);
}
return someValue;
} });
}
Inside a service, I would like to load a resource using $http. Once loaded resource, I want to store it in a variable. Then, I need to load a child resource and store it too. I know that the promise is designed for this kind of work, but there seems to be so much how to use it I get a little confusion. Here is my code:
var project = {};
var todo = {};
function init(){
var idProject = 21;
var idTodo = 6;
// If idProject is specified
if ( idProject != null ) {
// First, load project data
var promise = WorkspaceManager.getProject($rootScope.workspace, idProject);
// Then save project data
promise.then(function(response){
project = response.data;
return project;
});
if ( idTodo != null ) {
// Then load todo data
promise.then(function(project){
return ProjectManager.getTodo(project, idTodo);
});
// Then save todo data
promise.then(function(response){
todo = response.data;
return todo;
});
}
}
console.log(project); // returns {}
}
init()
Thanks in advance !
If I understand correctly, this is trickier than it appears at first glance. You appear to need a function that chains two asynchronous processes and returns a promise of a composite value comprising data acquired by the first and the second processes.
At least two approaches are available :
Easy but inelegant: In each asynchronous process, accumulate the required values as properties of an outer object.
Elegant but awkward: From each asynchronous process, accumulate the required values as properties of an object, a promise of which is returned.
The code below adopts the first approach :
function init() {
var idProject = 21;
var idTodo = 6;
var projectObj = {};//This object acts as a "bank" for asynchrounously acquired data.
if ( idProject != null ) {
return WorkspaceManager.getProject($rootScope.workspace, idProject).then(function(response) {
projectObj.project = response.data;//put `response.data` in the bank as .project.
if( idTodo != null ) {
return ProjectManager.getTodo(response.data, idTodo);
}
}).then(function(toDo) {
projectObj.toDo = toDo;//put `toDo` in the bank as .toDo .
return projectObj;
});
}
}
init().then(function(projectObj) {
console.log(projectObj.project);
console.log(projectObj.toDo);
});
Or (still the first approach) with error handlers :
function init() {
var idProject = 21;
var idTodo = 6;
var projectObj = {};//This object acts as a "bank" for asynchrounously acquired data.
if ( idProject != null ) {
return WorkspaceManager.getProject($rootScope.workspace, idProject).then(function(response) {
projectObj.project = response.data;//put `response.data` in the bank as .project.
if( idTodo != null ) {
return ProjectManager.getTodo(response.data, idTodo);
}
else {
$q.defer().reject('idTodo invalid');
}
}).then(function(toDo) {
projectObj.toDo = toDo;//put `toDo` in the bank as .toDo .
return projectObj;
});
}
else {
return $q.defer().reject('idProject invalid');
}
}
init().then(function(projectObj) {
console.log(projectObj.project);
console.log(projectObj.toDo);
}, function(errorMessage) {
console.log(errorMessage);
});
untested
The way you doing, you're creating "brothers" promise derived from the first promise. All the promises are going to be resolved as soon as WorkspaceManager.getProject promise has been resolved. What I believe you want is to chain them all, in way that when first promise gets resolved, yo asks for Todo data, when you got it, you asks to save it. If this is the case, you shall grab the derived promise from each promise.
// Then save project data
promise = promise.then(function(response){
project = response.data;
return project;
});
// Then load todo data
promise = promise.then(function(project){
return ProjectManager.getTodo(project, idTodo);
});
// Then save todo data
promise.then(function(response){
todo = response.data;
return todo;
});
Trying to illustrate a bit more, the first approach is like:
var mainPromise = ...;
mainPromise.then(function loadTodo(mainPromiseReturn){});
mainPromise.then(function saveTodo(mainPromiseReturn){});
The loadTodo and saveTodo are pararell, they're not chained to each other. They both receive the same data.
The approach I suggest is like:
var mainPromise = ...;
mainPromise
.then(function loadTodo(mainPromiseReturn){})
.then(function saveTodo(loadTodoReturn){});
I am building a mobile app using phonegap, jQuery and jQuery mobile. I want to use SqLite database in my app for storing some user information. (I can't use local storage i want to do search/sort operations on the data)
This is the code I am working on to get this done,
function getAccountInformation(){
var accounts = {};
db.transaction(function(transaction) {
transaction.executeSql('SELECT * FROM account;', [],
function(transaction, result) {
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
var item={};
var row = result.rows.item(i);
for(var prop in row){
item[prop]=row[prop]
}
accounts[i]=item;
}
}
},errorHandler
);
},errorHandler,nullHandler);
console.log(JSON.stringify(accounts));
}
If I put this console.log(JSON.stringify(accounts)); after the end } of the for loop it shows proper output.
But if I put it where it is right now the {} is printed as an output.
How can I make getAccountInformation() function return that accounts object to my other function? Where I will use jQuery to render the output.
What I want to do is return this accounts object simply by wrting
return accounts;
Because the SqLite functions are asynchronous you cannot just return the value.
I would make the getAccountInformation receiving a callback as below:
function getAccountInformation(callbackfn)
{
db.transaction(function(transaction),
.....,
function (transaction, result)
{
if (result != null)
{
callbackfn(result);
}
});
}
In such way you will get your function called when the db request executed.
That depends on when the function is called. When it is called asynchronously (like an AJAX request) you're out of luck. In that case I suggest you read about jQuery deferreds.
A code snippet based on deferreds could look like this:
var deferred = new jQuery.Deferred();
var accounts = {};
deferred.done(function(data) {
// process your data here
});
db.transaction(function(transaction) {
transaction.executeSql('SELECT * FROM account;', [],
function(transaction, result) {
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
var item={};
var row = result.rows.item(i);
for(var prop in row){
item[prop]=row[prop]
}
accounts[i]=item;
}
deferred.resolve(accounts); // let the "done" function get called
} else {
deferred.reject();
}
}, errorHandler
);
},errorHandler,nullHandler);
db.transaction and transaction.executeSql produce their results asynchronously through the use of callback functions. This means that your getAccountInformation function will return immediately and will not wait for the transaction to complete (as in a synchronous call).
It's probably easier to simply pass in a callback function as an argument of your getAccountInformation and run that function when the accounts array is populated. Therefore, change your function signature to getAccountInformation(callback) and replace the executeSql callback with:
function(transaction, result) {
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
var item={};
var row = result.rows.item(i);
for(var prop in row){
item[prop]=row[prop]
}
accounts[i]=item;
}
callback(accounts); // Call callback function with populated accounts
}
}
You can then call this function with:
getAccountInformation(function(accounts) {
// Do something with the accounts
});
There are fancier ways such as jQuery Deferreds and Promises which makes working with asynchronous functions easier, but you still need to understand why this exists. By using asynchronous actions, your application stays responsive while waiting for results.