UPDATE - Solution:
I was for looping instead should have used MongoDBs $in feature - details here
I have the following Q.all block:
Q.all([
getFirstBlock(),
getSecondBlock(),
])
.then(function(){
getSecondBlockProcessed();
})
.then(function(){
res.json(completeArray);
});
The problem I have is when I go into the final then block I notice that the function getSecondBlockProcessed has not been completed.
Everything in the first Q.all is done.
Why isn't that promise getting resolved?
And the method in question looks like:
var getSecondBlockProcessed = function() {
return Q.promise(function(resolve, reject) {
for (var i=0; i<mostInRegion.length; i++){
Person.find(
{_id: mostInRegion[i]['_id']},
{question:1, country: 1},
function(err, found) {
mostInRegion2.push(found);
})
}
resolve();
});
}
Any help would be appreciated/what have i overlooked?
Thanks
Related
I have this code:
var services = ['EC2', 'S3', 'RDS', 'IAM']
var promises = [];
for(var i = 0; i < services.length; i++) {
var promise = awsStatus('us-east-1', services[i])
promises.push(promise);
console.log(promises);
}
q.all(promises).then(function(data){
console.log(promises);
});
it's supposed to loop on the services array with the awsStatus method. The problem is that sometimes I get all the results I want:
{ service: 'IAM', status: 0 }
{ service: 'EC2', status: 0 }
{ service: 'RDS', status: 0 }
But sometimes I get incomplete results. I thought I needed .then after awsStatus but that also didn't resolve this. What else is wrong in this piece of code?
since now there's a comment, I've also tried this :
var services = ['EC2', 'S3', 'RDS', 'IAM']
var promises = [];
for(var i = 0; i < services.length; i++) {
var promise = awsStatus('us-east-1', services[i]).then(function(promise){
promises.push(promise);
//console.log(promises);
});
}
q.all(promises).then(function(data){
console.log(promises);
});
and it produces the same results.
Try using .each
Promise.each(services, function(service) {
return awsStatus('us-east-1', service).then(function(promise){
promises.push(promise);
//console.log(promises);
});
}).then(function() {
console.log('Done with all instances');
});
I can suggest a solution using async library as follows:
var services = ['EC2', 'S3', 'RDS', 'IAM']
async.map(services,
function(item, done){
awsStatus('us-east-1', item).then(
function(){
done(null, "Completed item " + item);
},
function(err){
done(err + " in item " + item, null);
}
);
},
function(err, results){
if(err) return console.log(err);
console.log(results);
}
);
It will fail and stop executing if one of the promises returns an error. If dont want to stop you can return null in the error function of promise instead of error message as the first argument of done method
There are two problems in your code:
Using q.all() does not guarantee that the code will wait for all the promises to resolve. If one of them is rejected, the then method would be called, not waiting for the rest of the promises. You can solve that by using q.allResolved() instead.
You are logging the wrong objects. You should be logging the results of the promises, and not the promise objects themselves.
Example of how to solve both problems:
q.allResolved(promises).then(function(results) {
console.log(results);
}).catch(function(err) {
console.error(err);
});
I'm new to node and I'm having an issue with resolving an async Promise. My promise isn't resolving and I'm not sure what I did wrong. I'm still having troubles understanding promises and callbacks so any feedback is helpful.
var filterFiles = function(){
return new Promise(function(resolve, reject){
fs.readdir(rootDir, function(err, files){
if(err) return console.log(err);
var task = function(file){
return new Promise(function(resolve, reject){
if(! /^\..*/.test(file)){
fs.stat(rootDir + '/' + file, function(err, stats){
if(stats.isDirectory()){
dirArray.push(file);
console.log(dirArray.length);
resolve(file);
}
if(stats.isFile()){
fileArray.push(file);
console.log(fileArray.length);
resolve(file);
}
})
}
})
};
var actions = files.map(task);
return Promise.all(actions).then(function(resolve, reject){
resolve({dirArray: dirArray, fileArray: fileArray});
});
})
})
}
filterFiles().then(function(data){
console.log(data);
var obj = {
fileArray: fileArray,
dirArray: dirArray
};
res.send(obj);
})
It see at least three errors:
When you hit this if statement if(! /^\..*/.test(file)){ and it does not execute the if block, then the parent promise is never settled.
There is no error handling on fs.stat() so if you get an error on that call, you are ignoring that and will be attempting to use a bad value.
The error handling on your call to fs.readdir() is incomplete and will leave you with a promise that is never settled (when it should be rejected).
For a robust solution, you really don't want to be mixing promises and callbacks in the same code. It leads to the opportunity for lots of mistakes, particularly with error handling (as you can see you had at least three errors - two of which were in error handling).
If you're going to use Promises, then promisify the async operations you are using at the lowest level and use only promises to control your async code flow. The simplest way I know of to promisify the relevant fs operations is to use the Bluebird promise library with its Promise.promisifyAll(). You don't have to use that library. You could instead manually write promise wrappers for the async operations you're using.
Here's a version of your code using the Bluebird promise library:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
function filterFiles() {
return fs.readdirAsync(rootDir).then(function(files) {
let fileArray = [];
let dirArray = [];
// filter out entries that start with .
files = files.filter(function(f) {
return !f.startsWith(".");
});
return Promise.map(files, function(f) {
return fs.statAsync(f).then(function(stats) {
if (stats.isDirectory()) {
dirArray.push(f);
} else {
fileArray.push(f);
}
});
}).then(function() {
// make the resolved value be an object with two properties containing the arrays
return {dirArray, fileArray};
});
});
}
filterFiles().then(function(data) {
res.json(data);
}).catch(function(err) {
// put whatever is appropriate here
res.status(500).end();
});
This was rewritten/restructured with these changes:
Use promises for all async operations
Fix all error handling to reject the returned promise
Filter out files starting with a . synchronously before processing any files (simplifies async processing).
Use Promise.map() to process an array of values in parallel.
In the filterFiles().then() handler, handle errors
You can't res.send() a Javascript object so I used res.json(data) instead (though I'm not sure what exactly you really want to send).
Replace regex comparison with more efficient and simpler to understand .startsWith().
If you don't want to use the Bluebird promise library, you can make your own promise wrappers for the fs methods you use like this:
fs.readdirAsync = function(dir) {
return new Promise(function(resolve, reject) {
fs.readdir(dir, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
fs.statAsync = function(f) {
return new Promise(function(resolve, reject) {
fs.stat(f, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
function filterFiles() {
return fs.readdirAsync(rootDir).then(function(files) {
let fileArray = [];
let dirArray = [];
// filter out entries that start with .
files = files.filter(function(f) {
return !f.startsWith(".");
});
return Promise.all(files.map(function(f) {
return fs.statAsync(f).then(function(stats) {
if (stats.isDirectory()) {
dirArray.push(f);
} else {
fileArray.push(f);
}
});
})).then(function() {
// make the resolved value be an object with two properties containing the arrays
return {dirArray, fileArray};
});
});
}
filterFiles().then(function(data) {
res.json(data);
}).catch(function(err) {
res.status(500).end();
});
The main issue you are having is that outer-most Promise is not resolved or rejected. You can fix this by resolving your Promise.all instead of returning it.
resolve(
Promise.all(actions)
.then(function(resolvedTasks){
// ... next potential issue is here
return {dirArray: dirArray, fileArray: fileArray}
})
);
(I know, kind of awkward-looking right?)
Next, your return value after the Promise.all resolves is a little weird. In the task function, you're pushing items onto dirArray and fileArray, but they are not declared or assigned in your snippet. I will assume that they are in-scope for this code. In this case, you just need to return your desired object.
Additionally, to make your async code more readable, here are some tips:
try not to mix callbacks with Promises
use a Promise library to promisify any code limited to callbacks. Example: bluebird's promisifyAll
avoid nesting callbacks/promises when possible
I want to chain 4 functions in a Promise chain like so:
function1 -> function2 -> function3 -> function4
My Promise chain
if ($location.$$url !== "/dashboard") {
vm.customURL = true;
// (1) Set root vars & Rebuild tickerTagsContainer:
var promise = TagFactory.buildUrlObject($location.$$url).then(function() {
console.log('TagFactory.buildUrlObject PROMISE returned');
}).then(function() {
console.log('(2) Re-display tags in viewHeader');
// (2) Re-display tags in viewHeader:
viewHeader = ScopeFactory.getScope('viewHeader');
viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
});
}).then(function() {
// (3) Reselect timeSpan:
console.log('(3) Reselect timeSpan');
viewHeader.vh.toggleTimeSpan(vm.timeSpan);
// (4) Refresh URL:
console.log('(4) Refresh URL');
ViewFactory.remakeViewObject($location.$$url);
});
}
The resulting console.logs:
^ Note I never see this log:
viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
});
Ideally I want to place my (3) function inside that, then chain my (4) like so:
viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
console.log('(3) Reselect timeSpan');
viewHeader.vh.toggleTimeSpan(vm.timeSpan).then(function() {
console.log('(4) Refresh URL');
ViewFactory.remakeViewObject($location.$$url);
});
});
However I never see the console.log from the .then function for displayViewHeaderTags
Here is what my displayViewHeaderTags looks like:
function displayViewHeaderTags() {
vm.viewTickerTags = [];
vm.viewTickerTags = TagFactory.retrieveTickerTags('all');
var deferred = $q.defer();
var tikObjs = vm.viewTickerTags.map(function(el) { return el.ticker; });
var tagObjs = vm.viewTickerTags.map(function(el) { return el.tags; });
var tags = _.flatten(tagObjs);
// forEach loops up to 3 times:
tags.forEach(function(tag, i) {
vm.viewTags = [];
ApiFactory.getTagDataSilm(tag.term_id).then(function(data) {
vm.viewTags.push(data.data.ticker_tag);
if (i === tags.length) {
deferred.resolve();
}
});
});
return deferred.promise;
}
Inside my displayViewHeaderTags function I hit a loop which will run up to 3 times, after it's done getting data, it will fill up and array then calls deffered.resolve. then returns it return deferred.promise;
So why do I never see this log? console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
Your i is never the same as the length, because the i variable starts at zero (array indexes start at zero). Which means the if you have an array with length = 2, your i values will be 0 and 1 respectively. It will never equal to zero. Basically, you would want the condition to be:
vm.viewTags.push(data.data.ticker_tag);
if (i + 1 === tags.length) {
deferred.resolve();
}
Anyway, using defer() is a code smell.
A more elegant way of doing it would be using $q.all
var allPromises = [];
var promise;
tags.forEach(function(tag) {
vm.viewTags = [];
promise = ApiFactory.getTagDataSilm(tag.term_id).then(function(data) {
vm.viewTags.push(data.data.ticker_tag);
});
// Create an array of promises, one promise for each request
allPromises.push( promise );
});
// Return a new promise that will only be resolved
// when all the promises of the array `allPromises` are resolved,
// or is rejected when one of them is.
return $q.all( allPromises );
Your chain is not really doing anything since you're not returning a promise from any of those anonymous functions. You're not seeing that log probably because ApiFactory.getTagDataSilm is failing or never resolving. Try adding an error handler into your flow.
if ($location.$$url !== "/dashboard") {
vm.customURL = true;
// (1) Set root vars & Rebuild tickerTagsContainer:
var promise = TagFactory.buildUrlObject($location.$$url).then(function() {
console.log('TagFactory.buildUrlObject PROMISE returned');
}).then(function() {
console.log('(2) Re-display tags in viewHeader');
// (2) Re-display tags in viewHeader:
viewHeader = ScopeFactory.getScope('viewHeader');
return viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
});
}).then(function() {
// (3) Reselect timeSpan:
console.log('(3) Reselect timeSpan');
return viewHeader.vh.toggleTimeSpan(vm.timeSpan);
}).then(function() {
// (4) Refresh URL:
console.log('(4) Refresh URL');
return ViewFactory.remakeViewObject($location.$$url);
}).catch(function(error) {
console.log('Something failed', error);
});
}
Within displayViewHeaderTags, you can use $q.all, so that rejections are handled for you:
// forEach loops up to 3 times:
vm.viewTags = [];
return $q.all(_.map(tags, function(tag) {
return ApiFactory.getTagDataSilm(tag.term_id).then(function(data) {
vm.viewTags.push(data.data.ticker_tag);
});
}));
Background: I have a PHP background and this is my first application using MEAN stack.
I need to save a record but before I must to check if there is any record under the same id already saved in the DB.
In PHP I would do something like this:
Once the user clicks "Save":
1) Call the function to check if an entry with that id already exists
2) If it doesnt, call the save function.
In Javascript, I'm getting a little confused with Promises and so on.
Can somebody give me some light here?
Right now, I'm doing the following:
In the save api, I call this function to check if the record already exists in the DB:
recordExists = findTranscationByBill(billId);
function findTransactionByBill(billId){
results = new promise(function(resolve, reject){
Transactions.find({billId : billId},function(err, transactions){
if(err)
reject("Error: "+err);
//console.log(transactions);
resolve(transactions);
});
});
results.then(function(data){
console.log('Promise fullfilled: '+ data);
}, function(error){
console.log('Promise rejected: ' + error);
});
return $results;
}
The problem is that I think I'm not using promise properly, as my variable doesn't get populated (because its Async).
In the console.log I see that the promise is being fulfilled however, the variable returns as [object Object]
I'm stucked with this problem because I don't know if I should carry on thinking as PHP mindset or if there is a different approach used in Javascript.
Thanks in advance!
In my opinion you could just as well use a callback for this, and since MongoDB has a count method, why not use it
function findTransactionByBill(billId, callback){
Transactions.count({billId : billId}, function(err, count){
if (err) {
callback(err, false);
} else {
callback(null, count !== 0);
}
});
}
and to use it
findTransactionByBill(billId, function(err, exists) {
if (err) {
// handle errors
} else if ( ! exists ) {
// insert into DB
}
}
I think the right function is:
function findTransactionByBill(billId){
var results = new promise(function(resolve, reject){
Transactions.find({billId : billId},function(err, transactions){
if(err) {
reject(err);
} else {
if (transactions.length === 0) {
reject('No any transaction');
} else {
//console.log(transactions);
resolve(transactions);
}
});
});
results.then(function(data){
console.log('Promise fullfilled: '+ data);
}, function(error){
console.log('Promise rejected: ' + error);
});
return results;
}
And then use it like this:
recordExists = findTranscationByBill(billId);
recordExists.then(function() {
// resolved, there are some transactions
}, function() {
// rejected. Error or no any transactions found
// may be you need to check reject result to act differently then no transactions and then error
});
I assume you are using mongodb native drive.
I think mongodb doesn't have promise built-in supported. So you have to promisify it by a little help from promise library. Please refer this if you want to use bluebird.
After promisifying, the code should looks like that (using bluebird):
Promise = require('bluebird');
// Promisify...
var _db = null;
var client = MongoClient.connectAsync('mongodb://localhost:27017/test')
.then(function(db) {
_db = db
return db.collection("myCollection").findOneAsync({ id: 'billId' })
})
.then(function(item) {
if (item)
_db.save(item);
})
.catch (err) {
// error handling
}
The above code is not perfect, because it introduced a global var, so the better version may be
Promise = require('bluebird');
// Promisify...
var client = MongoClient.connectAsync('mongodb://localhost:27017/test')
.then(function(db) {
return Promise.prop({
item: db.collection("myCollection").findOneAsync({ id: 'billId' },
db: db
})
})
.then(function(result) {
var item = result.item;
var db = result.db
if (item)
db.save(item);
})
.catch (err) {
// error handling
}
You need to check bluebird to know how to use it. Also they are many other promise libraries like q, when, but all are similar stuff.
I'm attempting to run a Parse.Query in my matchCenterComparison function, which is part of the main promise chain below.
When I run this code, it logs out ('setup query criteria, about to run it'); and ('MatchCenterComparison Succeeded bro!'), but not the console.log within userCategoryThingQuery.find().then.
I've researched this online, and looked through the Parse.Query Documentation, and my conclusion is that the main promise chain isn't waiting for userCategoryThingQuery to finish, since it's asynchronous. Is this what's causing the problem? If so, how can I fix this?
Main Promise Chain:
Parse.Cloud.job("MatchCenterBackground", function(request, status) {
// ... other code to setup usersQuery ...
var usersQuery = new Parse.Query(Parse.User);
usersQuery.each(function (user) {
return processUser(user).then(function(eBayResults){
return matchCenterComparison(eBayResults);
});
}).then(function() {
status.success("background job worked brah!");
}, function(error) {
status.error(error);
});
});
matchCenterComparison Function:
function matchCenterComparison(eBayResults) {
console.log('eBayResults are the following:' + eBayResults);
var matchCenterComparisonPromise = new Parse.Promise();
if (eBayResults.length > 0) {
// do some work, possibly async
console.log('yes the ebay results be longer than 0');
var userCategoryThing = Parse.Object.extend("userCategory");
var userCategoryThingQuery = new Parse.Query(userCategoryThing);
userCategoryThingQuery.contains('categoryId', '9355');
console.log('setup query criteria, about to run it');
userCategoryThingQuery.find().then(function(results) {
console.log('lets see what we got here:' + results);
});
matchCenterComparisonPromise.resolve(console.log('MatchCenterComparison Succeeded bro!'));
} else {
matchCenterComparisonPromise.reject({ message: 'No work done, expression failed' });
}
return matchCenterComparisonPromise;
}
Your problem is here:
function(request, status) {
// ... other code to setup usersQuery ...
var usersQuery = new Parse.Query(Parse.User);
usersQuery.each(function (user) {
return processUser(user).then(function(eBayResults){
return matchCenterComparison(eBayResults);
});
})
Here's a question - what does this function return?
Answer - it returns undefined. It doesn't return a promise, and therefore the chain has nothing to wait on.
What you need to do is take all the promises from your loop over usersQuery and return a promise that doesn't complete until they all do. Try rewriting like this:
function(request, status) {
// ... other code to setup usersQuery ...
var usersQuery = new Parse.Query(Parse.User);
return usersQuery.each(function (user) {
return processUser(user).then(function(eBayResults){
return matchCenterComparison(eBayResults);
});
}))
Looking at the docs for Parse.Query, the important bits are this:
If the callback returns a promise, the iteration will not continue
until that promise has been fulfilled.
and
Returns: {Parse.Promise} A promise that will be fulfilled once the
iteration has completed.
So this should get you what you want - the usersQuery.each call will return a promise that completes when the iteration ends, and returning the promise from inside the callback will mean the iteration doesn't complete until after all the items have been processed.