Return when .each is completed - arrays

Promise.try(function (){
return Promise.all(splitup); // [Show, me, stocks, for, Google, and, Microsoft]
}).each(function (item) { // Looping through entities, [Show] [me] [stocks] ...
alchemyapi.entities('text', item, { 'sentiment' : 1}, function (response) {
if(response.entities) { // An entity is found, ex. Microsoft
if(response.entities[0].type === "Company") {
requestBody.push(item);
console.log("Item was found, added " + item);
} else {
console.log(item + " Is not a company");
}
} else { // no entity found for that one word
console.log("No entity found for " + item);
}
});
}).then(function (response) {
// send requestBody when loop is completed.
});
I start by returning an array of strings splitup so I can loop through each element on line 3.
Let's say the splitup array looks like: [Apple, And, Mexico]
Apple is a company, so if(response.entities) returns true, it then checks the JSON response to see if it is a company, that statement returns true and It's added to the new requestBody array I'm building up.
Next, the word 'And' returns false on if(response.entities) so It goes to the else statement.
Next, let's pick Mexico, It'll return true for if(response.entities) but return false on if(response.entities[0].type === "Company")
My question is, I'd like to return the new requestBody array when It has completed looping through each item, but I'm not entirely sure how I can tell when the loop is completed, and when to return requestBody

You need to use Promise.filter instead of Promise.each. Promise.filter filters the given array to another using filterer function passed to it.
So when you encounter a company ('Apple') you resolve with its value, if its anything else ('Mexico' and 'And') you resolve with false.
Promise.filter(splitup, function (item) { // Looping through entities, [Show] [me] [stocks] ...
return new Promise(function(resolve, reject) {
alchemyapi.entities('text', item, { 'sentiment' : 1}, function (response) {
if(response.entities) { // An entity is found, ex. Microsoft
if(response.entities[0].type === "Company") {
console.log("Item was found, added " + item);
return resolve(item);
} else {
console.log(item + " Is not a company");
return reject(false);
}
} else { // no entity found for that one word
console.log("No entity found for " + item);
return reject(false);
}
});
});
}).then(function (requestBody) {
// send requestBody when loop is completed.
});

Ok too late :). Here was my result:
var alchemyapi = require('alchemy-api');
var Promise = require('bluebird');
var alchemyapi = new alchemyapi(<YOUR_KEY>);
var test = ['Microsoft', 'and', 'Apple'];
Promise.filter(test, function(item) {
return getCompanyName(item).then(function(){
return true;
}, function(reason) {
console.log(reason.message);
return false;
});
}).then(function(requestBody){
console.log(requestBody);
});
function getCompanyName(item) {
return new Promise(function(resolve, reject) {
alchemyapi.entities(item, {sentiment: 1}, function (err, response) {
if (err) reject(err);
if (response.entities.length > 0) { // An entity is found, ex. Microsoft
if (response.entities[0].type === "Company") {
resolve(item);
} else {
reject(new Error(item + " Is not a company"));
}
} else { // no entity found for that one word
reject(new Error("No entity found for " + item));
}
});
});
}

Related

Array populated in debug more but not in in normal mode in Node.js

In the code below, when I run in debug mode with a break-point at this line: content.push(data.Body.toString()); I can see that data is inserted to the content array.
However when I run the code normally, content comes back empty.
How can I get it to populate the array for downstream use?
var params = { Bucket: "thebucket", Prefix: "theprefix/" }
var content = [];
function getS3Data()
{
var s3 = new aws.S3();
s3.listObjects(params, function (err, data)
{
if (err) throw err; // an error occurred
else
{
var i;
for (i = 0; i < data.Contents.length; i++)
{
var currentValue = data.Contents[i];
if(currentValue.Key.endsWith(params.Prefix) == false)
{
var goParams = { Bucket: params.Bucket, Key: currentValue.Key };
s3.getObject(goParams, function(err, data)
{
if (err) throw err; //error
content.push(data.Body.toString());
});
};
};
}//else
});//listObjects
}//getS3Data
getS3Data();
console.log(content); //prints empty here when run in non-debug.
The line:
console.log(content)
is being executed before the line:
content.push(data.Body.toString());
the function you are passing as a 2nd argument to s3.listObjects will be executed asynchronously. If you want to log out content you need to do it within the callback function meaning:
s3.listObjects(params, function (err, data) {
if (err) throw err;
else {
// ...
console.log(content)
}
});
A better approach would be to implement getS3Data with Promise so you can run code after the object listing is done for sure.
function getS3Data() {
return new Promise((resolve, reject) => {
if (err) {
reject(err)
} else {
const promises = []
for (const i = 0; i < data.Contents.length; i++) {
const currentValue = data.Contents[i];
if (currentValue.Key.endsWith(params.Prefix) == false) {
const goParams = { Bucket: params.Bucket, Key: currentValue.Key };
promises.push(new Promise((res, rej) => {
s3.getObject(goParams, function (err, data) {
if (err) {
rej(err); //error
} else {
res(data.Body.toString());
}
});
}));
}
}
Promise.all(promises).then(resolve);
}
});
}
getS3Data()
.then(result => { // this will actually be `content` from your code example
console.log(result);
}).catch(error => {
console.error(error);
})
Node.js' documentation has an example very similar to the problem you are experiencing:
Dangers of Mixing Blocking and Non-Blocking Code
The issue arises because the variable content is not set as soon as getS3Data has finished, because it is an asynchronous function. content will be set some time later. But your call to console.log(content); will execute immediately after getS3Data has finished, so at that point content has not been set yet.
You can test that by adding an extra log:
s3.getObject(goParams, function(err, data)
{
if (err) throw err; //error
content.push(data.Body.toString());
console.log("Content has been assigned");
});
And then change the bottom to:
getS3Data();
console.log("getS3Data has finished", content);
It's likely you'll get the messages in this order:
getS3Data has finished
Content has been assigned

Can't compare MongoDB data with javascript array

I want to compare the data which I got from Mongo to javascript array. I am using lodash to compare. But it always return incorrect result.
var editUser = function(userData, getOutFunction) {
var status = CONSTANTS.NG;
checkExistUser(userData._id).then(function(user) {
if (user !== null) {
var userGroup = JSON.stringify(user.group);
user.group = user.group.map((groupId) => {
return groupId.toString();
});
var removedGroups = _.difference(userGroup, userData.group);
var addedGroups = _.difference(userData.group, userGroup);
console.log('Removed Groups: ', removedGroups);
console.log('Added Groups: ', addedGroups);
} else {
status = CONSTANTS.NG;
logger.debug(DEBUG_CLASS_NAME, "Cannot find object");
if (typeof(getOutFunction) !== 'undefined') {
getOutFunction(status, null);
} else {
NO_CALLBACK();
}
}
}).catch(function() {
console.log('Promise is error');
});
var checkExistUser = function(userId) {
return new Promise(function(resolve, reject) {
UserDAO.findById(userId, function(err, user) {
if (err) {
logger.debug(DEBUG_CLASS_NAME, {
name: err.name,
code: err.code,
message: err.message,
method: "checkExist"
});
resolve(null);
} else {
resolve(user);
}
});
});
}
For example:When I try to input value for lodash difference function
var user.group = ["58b8da67d585113517fed34e","58b8da6ed585113517fed34f"];
var userData.group = [ '58b8da67d585113517fed34e' ];
I want lodash difference return below result:
Removed Groups: ['58b8da6ed585113517fed34f']
Added Groups: []
However, the function gave me the result like:
Removed Groups: []
Added Groups: [ '58b8da67d585113517fed34e' ]
Can anyone help me in this case?
I will do appreciate it.
I have had this issue as well, the result from mongodb is an ObjectId type so you can compare the someObjectId.toString() value with your array of strings, or you could use
someObjectId.equals(stringOrObjectIdValue)
However, if you want to keep using lodash functions you will either have to force both arrays to strings or to ObjectIds before passing them into the function.

pass to error case when function returns a rejected promise in angular

I need to return a rejected promise from a js function. I am using angular $q as you can see. But it doesn't work.
In function getDBfileXHR, when the promise getDBfileXHRdeferred is rejected using getDBfileXHRdeferred.reject() I would to pass into the the error case of the function getDBfileXHR and run fallbackToLocalDBfileOrLocalStorageDB(). But it doesn't work.
Is there a syntax error ?
I am a bit new to promises.
Thanks
this.get = function () {
var debugOptionUseLocalDB = 0,
prodata = [],
serverAttempts = 0;
if (debugOptionUseLocalDB) {
return fallbackToLocalDBfileOrLocalStorageDB();
}
if (connectionStatus.f() === 'online') {
console.log("Fetching DB from the server:");
return getDBfileXHR(dbUrl(), serverAttempts)
.then(function () { // success
console.log('-basic XHR request succeeded.');
return dbReadyDeferred.promise;
}, function () { // error
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
});
}
}
function getDBfileXHR(url, serverAttempts) {
var getDBfileXHRdeferred = $q.defer(),
request = new XMLHttpRequest();
if (typeof serverAttempts !== "undefined") serverAttempts++;
request.open("GET", url, true); //3rd parameter is sync/async
request.timeout = 2000;
request.onreadystatechange = function () { // Call a function when the state changes.
if ((request.readyState === 4) && (request.status === 200 || request.status === 0)) {
console.log('-we get response '+request.status+' from XHR in getDBfileXHR');
var jsonText = request.responseText.replace("callback(", "").replace(");", "");
if (jsonText === '') {
console.error('-error : request.status = ' + request.status + ', but jsonText is empty for url=' + url);
if (serverAttempts <= 2){
sendErrorEmail("BL: jsonText is empty, trying to reach server another time", 11);
getDBfileXHR(url, serverAttempts);
return;
} else {
sendErrorEmail("BL: jsonText is empty and attempted to reach server more than twice", 14);
var alertPopup = $ionicPopup.alert({
title: 'Error '+"11, jsonText is empty",
template: "Sorry for the inconvenience, a warning email has been sent to the developpers, the app is going to restart.",
buttons: [{
text:'OK',
type: 'button-light'
}]
});
getDBfileXHRdeferred.reject();
}
} else {
}
} else {
console.error('-error, onreadystatechange gives : request.status = ' + request.status);
getDBfileXHRdeferred.reject();
}
};
if (url === "proDB.jsonp") {
console.log("-Asking local proDB.json...");
} else {
console.log("-Sending XMLHttpRequest...");
}
request.send();
return getDBfileXHRdeferred.promise;
}
EDIT:
I rewrote my function using this approach. It seems better and cleaner like this. But now can you help me handle the multiple attempds ?
function getDBfileXHR(url, serverAttempts) {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open("GET", url, true); request.timeout = 2000;
var rejectdum;
if (url === "proDB.jsonp") {
console.log("-Asking local proDB.json...");
} else {
console.log("-Sending XMLHttpRequest...");
}
request.onload = function () {
if ( (request.readyState === 4) && (request.status === 200 || request.status === 0) ) {
console.log('-we get response '+request.status+' from XHR in getDBfileXHR');
var jsonText = request.responseText.replace("callback(", "").replace(");", "");
if (jsonText === '') {
console.error('-error : request.status = ' + request.status + ', but jsonText is empty for url=' + url);
sendErrorEmail("BL: jsonText is empty, trying to reach server another time", 11);
sendErrorEmail("BL: jsonText is empty and attempted to reach server more than twice", 14);
var alertPopup = $ionicPopup.alert({
title: 'Error '+"11, jsonText is empty",
template: "The surfboard database could not be updated, you won't see the new models in the list, sorry for the inconvenience.",
buttons: [{
text:'OK',
type: 'button-light'
}]
});
console.log('oui on passe rejectdum')
rejectdum = 1;
reject({
status: this.status,
statusText: request.statusText
});
} else {
var parsedJson;
try {
parsedJson = JSON.parse(jsonText);
} catch (e) {
console.warn("Problem when trying to JSON.parse(jsonText) : ");
console.warn(e);
console.warn("parsedJson :");
console.warn(parsedJson);
}
if (parsedJson) {
var prodata = jsonToVarProdata(parsedJson);
console.log('-writing new prodata to localStorage');
console.log('last line of prodata:' + prodata[prodata-1]);
storageService.persist('prodata', prodata);
storageService.store('gotANewDB', 1);
}
resolve(request.response);
dbReadyDeferred.resolve();
}
}
};
request.onerror = function () {
reject({
status: this.status,
statusText: request.statusText
});
};
request.send();
});
}
Is it a clean way to do this to do several attempts :
return getDBfileXHR(dbUrl(), serverAttempts)
.then(function () { // success
console.log('-basic XHR request succeeded.');
return dbReadyDeferred.promise;
})
.catch(function (){
if (typeof serverAttempts !== "undefined") serverAttempts++;
console.log('on passe dans le catch, serverAttempts = ', serverAttempts)
if (serverAttempts < 2) {
return getDBfileXHR(dbUrl(), serverAttempts)
.then(function () { // success
console.log('-basic XHR request succeeded.');
return dbReadyDeferred.promise;
})
.catch(function (){
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
})
} else {
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
}
})
if you remove the code to retry (twice?) on failure your code would possibly work (haven't looked into that) -
the issue is, the only promise your calling code gets is that of the first attempt. If the first attempt fails, that promise is never resolved or rejected
You need to resolve the promise with the promise returned by getDBfileXHR(url, serverAttempts); - so, something like
if (serverAttempts <= 2){
sendErrorEmail("BL: jsonText is empty, trying to reach server another time", 11);
getDBfileXHRdeferred.resolve(getDBfileXHR(url, serverAttempts));
return;
} else {
Because if promise(1) resolves to a rejected promise(2), the result is that promise(1) rejects with the rejection value of promise(2)
This is how native Promises, and many many Promise/A+ compliant libraries work,
so this should be the case with $.defer if it follows the Promise/A+ spec

Set json array in nodejs async

I am new in nodejs and I want to make a json array by comparing id inside a loop but the MongoDB function does not wait for the loop to complete, so data is not coming out properly. It displays the data before the loop ends, below is the code:
router.get('/getallcountrydataup',function(req, res) {
Country
.where('isDeleted').equals(false)
.exec(function(err,cData){
if (!cData) {
res.json({'status':0,'message': 'No data found','data':[]});
} else{
async.waterfall([
function (done) {
var countryalldata = [];
for (var i = 0; i < cData.length; i++) {
var country_s = cData[i];
State.where('s_c_id').equals(country_s._id)
.exec(function(err, statedata){
country_s.statecount = statedata.length;
//console.log(country_s._id);
console.log(country_s.statecount);
});
countryalldata.push(country_s);
}
done(err, countryalldata);
// console.log(countryalldata);
},
function (countryalldata, done) {
console.log(countryalldata);
res.json({
'status': 1,
'message': 'Data found',
'data': countryalldata
});
}
]);
}
});
});
Here is output of the countryalldata variable printed before the loop will complete. I want to print its output after loop execution is complete.
State.where is asynchronous so it should be synchronized. For example with async.map
router.get('/getallcountrydataup',function(req, res) {
Country
.where('isDeleted').equals(false)
.exec(function(err,cData){
if (!cData) {
res.json({'status':0,'message': 'No data found','data':[]});
} else {
async.waterfall([
function (done) {
async.map(cData, function (country_s, done2) {
// Convert country_s into plain object. It's not a mongoose
// model anymore
country_s = country_s.toObject();
State.where('s_c_id')
.equals(country_s._id)
.exec(function(err,statedata){
if (err) {
done2(err);
return;
}
country_s.statecount = statedata.length;
console.log(country_s.statecount);
done2(null, country_s)
});
}, function(err, countryalldata) {
if (err) {
done(err);
}
else {
done(null, countryalldata)
}
});
},
function (countryalldata, done) {
console.log(countryalldata);
res.json({'status':1,'message': 'Data found','data':countryalldata});
}
]);
}
});
});

Adding to an array asynchronously in Node.js

I'm pretty new to this type of programming and I'm having some trouble populating an array from a nested call. I'm pretty sure this needs to be done using callbacks, but I'm having trouble wrapping my brain around it. Closures must also come into play here. I tried searching the web for a similar example but didn't find much.
Here is my original code. I tried a few different approaches but didn't pull it off.
TaskSchema.statics.formatAssignee = function(assignees) {
var users = [];
assignees.forEach(function(uid) {
mongoose.model('User').findById(uid, function(err, user) {
users.push({
name: user.name.full
, id: user.id
});
});
});
return users;
}
I really like the following pattern (recursion is the most elegant solution to async loops):
TaskSchema.statics.formatAssignee = function(assignees, callback) {
var acc = []
, uids = assignees.slice()
(function next(){
if (!uids.length) return callback(null, acc);
var uid = uids.pop()
mongoose.model('User').findById(uid, function(err, user) {
if (err) return callback(err);
acc.push({
name: user.name.full
, id: user.id
});
next();
});
})();
}
Check out async, it has an async foreach loop.
Edit
Here is the foreach method from the async library
async.forEach = function (arr, iterator, callback) {
if (!arr.length) {
return callback();
}
var completed = 0;
_forEach(arr, function (x) {
iterator(x, function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed === arr.length) {
callback();
}
}
});
});
};
var _forEach = function (arr, iterator) {
if (arr.forEach) {
return arr.forEach(iterator);
}
for (var i = 0; i < arr.length; i += 1) {
iterator(arr[i], i, arr);
}
};
you could do something like:
Give formatAssignee a callback.
Count down how many users you need to push onto users.
After you push the last one, invoke the callback with the parameter users.

Resources