Issues when placing individual coroutine functions into a class - node.js

So I have a function that works just fine.
Here's the code for context:
"use strict";
//Get the splunk sdk
var splunkjs = require('splunk-sdk');
//get promise dependencies
Promise = require("bluebird");
//get request dependencies
let request = require('request-promise');
exports.splunkQuery = Promise.coroutine(function*(){
try{
// Create a Service instance and log in
let service = Promise.promisifyAll(new splunkjs.Service({
username:"user",
password:"pass",
scheme:"https",
host:"mysplunk",
port:"8089",
version:"6.3.2"
}));
// Pass the search query
let searchQuery = yield Promise.resolve("search index=edex FIDELITY.CCB.LPS.PROD.MORTFILE* PNOD=CD.HNBNOLA RECI=CTRC");
// Search params (No Idea what these do)
let searchParams = yield Promise.resolve({
exec_mode: "blocking"
});
//get the job object from splunk
let job = yield service.searchAsync(searchQuery,searchParams);
//add the fetchJson function to the job object
job.fetchJsonAsync = fetchJsonAsync;
//get the results
let results = yield Promise.resolve(job.fetchJsonAsync());
//return the resuling value
console.log(results.events[0]._raw)
} catch (e){
console.log(e)
}
});
//A function that returns a promise
function fetchJsonAsync(){
//Create a result object and return it
var resultsArrayObject = {}
//return a promise
return new Promise (function(resolve,reject){
//display the job id
console.log("Search job id: " + this.sid);
this.results({}, function(err, results) {
var fields = results.fields;
var rows = results.rows;
//Create an array to hold all of the event objects
var eventsArray = [];
for(var i = 0; i < rows.length; i++) {
var values = rows[i];
//Create an empty object to hold the atributes of an event
var eventObject = {}
for(var j = 0; j < values.length; j++) {
var field = fields[j];
var value = values[j];
eventObject[field] = value;
}
eventsArray.push(eventObject);
}
//populated the results object
resultsArrayObject["events"]=eventsArray;
if(err){
//reject the promise if there is an error
reject(err);
console.error(err)
}else{
//fulfill the promise
resolve(resultsArrayObject);
}
})
}.bind(this)); //This needs to be binded to this function so that it can get the rest of the functions properly
}
And call the function like this:
var test = Promise.coroutine(function*(request,response){
let result = yield Promise.resolve(splunk.splunkQuery());
console.log(result);
});
test();
I know I'm probably doing something sketchy when I add my own function to the job object yielded by the search query, but it works.
I wanted to break every step into individual methods in a single class, because I didn't want to create a new connection every time I wanted to make a query. So I wrote this:
"use strict";
//Get the splunk sdk
var splunkjs = require('splunk-sdk');
//get promise dependencies
Promise = require("bluebird");
//Create Class
let Splunk = {};
//Define object variables
Splunk._service = {}
Splunk._serviceDetails = {}
Splunk._query = ""
Splunk._searchParameters = {}
//Define object functions
//Set the login information
Splunk.setServiceDetails = function(serviceDetails){
return new Promise (function(resolve,reject){
this._serviceDetails = serviceDetails;
resolve(true)
}.bind(this));
}
Splunk.getServiceDetails = function(serviceDetails){
return new Promise (function(resolve,reject){
resolve(this._serviceDetails)
}.bind(this));
}
//Start the service
Splunk.startService = function(){
return new Promise (function(resolve,reject){
this._service = Promise.promisifyAll(new splunkjs.Service(this._serviceDetails));
resolve(true)
}.bind(this));
}
//Start the service
Splunk.getService = function(){
return new Promise (function(resolve,reject){
resolve(this._service)
}.bind(this));
}
Splunk.setQuery = function(query){
return new Promise (function(resolve,reject){
this._query = query;
resolve(true)
}.bind(this));
}
Splunk.getQuery = function(query){
return new Promise (function(resolve,reject){
console.log("Getting Query")
resolve(this._query)
}.bind(this));
}
Splunk.setSearchParameters = function (searchParameters){
return new Promise (function(resolve,reject){
this._searchParameters = searchParameters;
resolve(true)
}.bind(this));
}
Splunk.getSearchParameters = function (searchParameters){
return new Promise (function(resolve,reject){
console.log("Getting Search Parameters")
resolve(this._searchParameters)
}.bind(this));
}
Splunk.execute = Promise.coroutine(function*(){
try{
// Create a Service instance and log in
let service = yield this.getService();
// Pass the search query
let searchQuery = yield this.getQuery();
// Search params (No Idea what these do)
let searchParams = this.getSearchParameters();
//get the job object from splunk
let job = yield service.searchAsync(searchQuery,searchParams);
//add the fetchJson function to the job object
job.fetchJsonAsync = this._fetchJsonAsync;
//get the results
let results = yield Promise.resolve(job.fetchJsonAsync());
//return the resuling value
return results
} catch (e){
console.log(e)
}
});
Splunk._fetchJsonAsync = function(){
//Create a result object and return it
var resultsArrayObject = {}
//return a promise
return new Promise (function(resolve,reject){
//display the job id
console.log("Search job id: " + this.sid);
this.results({}, function(err, results) {
var fields = results.fields;
var rows = results.rows;
//Create an array to hold all of the event objects
var eventsArray = [];
for(var i = 0; i < rows.length; i++) {
var values = rows[i];
//Create an empty object to hold the atributes of an event
var eventObject = {}
for(var j = 0; j < values.length; j++) {
var field = fields[j];
var value = values[j];
eventObject[field] = value;
}
eventsArray.push(eventObject);
}
//populated the results object
resultsArrayObject["events"]=eventsArray;
if(err){
//reject the promise if there is an error
reject(err);
console.error(err)
}else{
//fulfill the promise
resolve(resultsArrayObject);
}
})
}.bind(this)); //This needs to be binded to this function so that it can get the rest of the functions properly
}
module.exports = Splunk;
And I call it like this (I plan to abstract the order of execution after its working):
var test = Promise.coroutine(function*(request,response){
//let result = yield Promise.resolve(splunk.splunkQuery());
try{
yield splunkClass.setServiceDetails({
username:"user",
password:"pass",
scheme:"https",
host:"mysplunk",
port:"8089",
version:"6.3.2"
});
console.log("set service details");
yield splunkClass.startService();
console.log("started service");
yield splunkClass.setQuery("search index=edex FIDELITY.CCB.LPS.PROD.MORTFILE* PNOD=CD.HNBNOLA RECI=CTRC");
console.log("set query async")
yield splunkClass.setSearchParameters({
exec_mode: "blocking"
});
console.log("set search parameters")
let result = yield splunkClass.execute()
console.log("executed")
yield Promise.resolve(console.log(result));
Promise.resolve(response.send(result))
}catch(exception){
console.log(exception)
}
});
test();
But I get the following error when I run this version:
Search job id: 1470853434.636329
/Users/nit/Documents/Progs/JavaScript/he_dashboard/services/splunkClass.js:109
for(var i = 0; i < rows.length; i++) {
^
TypeError: Cannot read property 'length' of undefined
at /Users/nit/Documents/Progs/JavaScript/he_dashboard/services/splunkClass.js:109:30
I know that its entering the _fetchJsonAsync method and making the connection correctly, because its logging the sid (job id) before the error. It seems like the actual job object is different this time around and I can't seem to figure out why.
Thanks in advance,
Maz
PS: I'm still in the process of learning node, while trying to use it for something useful. Please let me know if I'm doing other stupid things along the way. Thanks!!

Related

NodeJS find query inside for loop results after the loops end

var user_id = '98-XXXXXXXX'
Contact.find({user_id: user_id})
.exec(function (err, results) {
if (err) {
return next(err);
}
var finalArray = [];
for(var i = 0; i< results[0].Total; i++) {
if(results[0].Contacts[i].name != "SPAM") {
for(var j = 0; j < results[0].Contacts[i].phoneNumbers.length; j++){
var number = results[0].Contacts[i].phoneNumbers[j].number
number = number.replace(/ /g,'');
var user_id = number.substr(number.length - 10);
Login.find({user_id:user_id})
.exec(function(err,results) {
if(err) {
return next(err); }
var intCount = results.length;
if (intCount > 0)
{
console.log('called')
finalArray.push(results[0])
console.log(finalArray)
}
});
}
}
//console.log(i,results[0].Total - 1);
//if(i == results[0].Total - 1)
}
console.log('Ended Here',finalArray)
var responseTosend = {"message":finalArray,"user_id":user_id}
return res.send(responseTosend);
});
EndedHere[] this is coming up first empty, after that i got the result of login.find query which is correct. Any ideas how to get the finalArray after all the calculation.
Thanks in advance
Since the functions are returning promises within the loops, the code execution has to wait till all those promises are resolved. Consider using Promise.all or Promise.map to wait. Reference
As already mentioned, a structure like this, will not return the results, but the intermediate functions or objects before they are finished, since nodejs does not know it should await the results first.
const x = [1,2,3]
let results = []
for (let i = 0; i < x.length; i++){
results.push(someAsyncJobLikeADatabaseCall(x[i]))
}
// this will not return the results, but the intermediate async objects/functions
console.log(results)
Here is a better version using promises and the .map function. Notice, how we replaced the for loop with .map() (which you could see as a shorthand for .forEach + push() or for() + push(). Mongoose returns Promises if configured right, so you don't even have to manually define them and we can directly return them in .map.
const x = [1,2,3]
let results = []
async function getAsyncResults(array){
// map returns an array, this time, an array of promises
const promises = x.map(number => someAsyncJobLikeADatabaseCall(number))
// Promise.all resolves, if all promises in the array have been resolved
return Promise.all(promises)
}
try {
let results = await getAsyncResults(x)
// this will return the results you expect.
console.log(results)
} catch (err) {
console.log('Some error', err)
}

How do I chain multiple promises while passing and updating an object through each promise with additional API calls?

I am new to Javascript and am just getting familiar with promises so forgive my ignorance.
What I'm trying to do is request all the records from an Airtable base and filter them them based on a checkbox called "Email Sent" being unsent. When I have the records filtered there are linked records in 2 fields that I need to do additional requests for to get the values for. All of those values (various data, and recipients as an object) then are plugged into an HTML email template and fired off using AWS-SES. Then the "Email Sent" check box is checked for those records to prevent emails from being sent multiple times. The plan is to have this all running on an interval in AWS Lambda. And as records are added to the base they are automatically emailed to a list.
I am able to get the records, and filter them. I am also comfortable with executing the code within Lambda and using SES. But I have been struggling for days to get the values for the linked records. Both of which are lists of people and 1 of which is a recipient email address.
The numerous things I've tried end up either returning the original record called in the first step or returns undefined. I'm chaining a lot of promises and I don't think I'm doing it right because it seems they are running out of order. I'm also at the point of trying weird things like using a global array instead of an object that I'm passing down through the promises and updating.
Any help is appreciated. Thank you in advance.
var Airtable = require('airtable');
var base = new Airtable({apiKey: 'xxxxxxxxxx'}).base('xxxxxxxxxx');
var nodemailer = require("nodemailer")
var handlebars = require('handlebars');
const path = require('path');
var AWS = require("aws-sdk")
var ses = new AWS.SES();
var fs = require('fs');
var mainArray = [];
var readHTMLFile = function(path, callback) {
fs.readFile(path, {encoding: 'utf-8'}, function (err, html) {
if (err) {
throw err;
callback(err);
}
else {
callback(null, html);
}
});
};
function getRecords(){
return new Promise(function(resolve, reject) {
var reqArr = [];
base('Edit Requests').select({
// Selecting the first 3 records in Grid view:
maxRecords: 50,
view: "Grid view"
}).eachPage(function page(records, fetchNextPage) {
// This function (`page`) will get called for each page of records.
records.forEach(function(record) {
// console.log("108: ", record._rawJson.id)
var obj = {}
// obj = record.fields;
// obj.id = record._rawJson.id;
console.log("172", record.fields["Editor Preference"])
obj = record.fields;
obj.id = record._rawJson.id;
if(record.fields["Editor Preference"] != undefined){
obj["Editor Preference"] = obj["Editor Preference"]
// obj["Editor Preference"] = getEditorWrap(record.fields["Editor Preference"])
} else {
obj["Editor Preference"] = "";
}
if(record.fields["Production Manager"] != undefined){
obj["Production Manager"] = obj["Production Manager"]
} else {
obj["Production Manager"] = "";
}
mainArray.push(obj)
// console.log(record.fields["Email"])
// console.log('Retrieved', record.fields['Requested By']);
})
// To fetch the next page of records, call `fetchNextPage`.
// If there are more records, `page` will get called again.
// If there are no more records, `done` will get called.
fetchNextPage();
// console.log("123", reqArr)
// resolve(reqArr)
}, function done(err) {
if (err) { console.error(err); return; }
// console.log("123", mainArray)
resolve(mainArray)
});
// resolve(reqArr)
});
}
function filterRecords(arr){
return new Promise(function(resolve, reject) {
var filtered = []
mainArray = [];
// console.log("245", arr)
for (i in arr){
if(arr[i]['Email Sent'] == undefined){
// console.log("247", arr[i])
mainArray.push(arr[i])
};
}
console.log("filtered: ", mainArray)
resolve(mainArray)
});
}
function setObject(array){
return new Promise(function(resolve, reject) {
for (i in array){
var obj = array[i];
if(obj.id != undefined){
base('Editors').find(obj.id, function(err, record) {
if (err) { console.error(err); return; }
// console.log("281", record.fields);
});
}
}
resolve(mainArray)
});
}
function main1(){
return new Promise(function(resolve, reject) {
getRecords().
then(function(recordArr){
filterRecords(mainArray).
then(function(resultArr){
setObject(mainArray).
then(function(mainArray){
})
})
})
});
}
main1().
then(function(resultArray){
console.log(resultArray)
})

return from forEach in node.js to loop through firebase results

How can I get a return after I loop through all my firebase results to properly finish my cloud function?
var count = 0;
return ref.child('/records').once('value').then(snap=>{
snap.forEach(snapChild=>{
var ucName = ${snapChild.val().name.toUpperCase();
var update = {'name':ucName);
ref.child(`/records/${snapChild.key}`).update(update).then(()=>{
count++;
res.write(`<p>${ucName} updated</p>`);
});
})
}).then(()=>{
return res.end(`<p>end of list (${count} records)</p>`);
})
It actually does what it is supposed to do but the counter stays at 0 and I get an error 'write after end' - I guess due to the missing return from forEach.
It's because the last then callback is called, when snap children is not processed. You need to use Array#map to produce an array of promises, and Promise.all to wait until all promises are not resolved:
var count = 0;
return ref
.child('/records').once('value')
.then(snap => {
let ops = snap.map(snapChild => {
var ucName = ${snapChild.val().name.toUpperCase();
var update = {'name':ucName);
return ref.child(`/records/${snapChild.key}`).update(update).then(() => {
count++;
res.write(`<p>${ucName} updated</p>`);
});
});
return Promise.all(ops);
})
.then(()=>{
return res.end(`<p>end of list (${count} records)</p>`);
});

Return value in function from a promise block

I'm trying to write a function (using WebdriverJS lib) that iterates through a list of elements, checks the names and build an xpath locator that corresponds to that name. I simplified xpath locators here, so don't pay attention.
The issues I'm facing here are:
1) Calling this function returns undefined. As far as I understand, this is because the return statement is not in its place, but:
2) Placing it in the correct place where a synchronous code would normally work, doesn't work for async promises, hence calling this function will return the same undefined, but because the return statement fires before the "driver.findElement" statement.
How should I use the return statement here, if I want to get createdTask variable as a result of calling this function?
var findCreatedTask = function() {
var createdTask;
driver.findElements(By.xpath("//div[#id='Tasks_Tab']")).then(function(tasks) {
for (var index = 1; index <= tasks.length; index++) {
driver.findElement(By.xpath("//div[#id='Tasks_Tab'][" + index + "]//div[#class='task-title']")).getText().then(function(taskTitle) {
if (taskTitle == "testName") {
createdTask = "//div[#id='Tasks_Tab'][" + index + "]";
return createdTask;
}
});
}
});
};
You could first get all the texts with promise.map and then get the position with indexOf :
var map = webdriver.promise.map;
var findCreatedTask = function() {
var elems = driver.findElements(By.xpath("//div[#id='Tasks_Tab']//div[#class='task-title']"));
return map(elems, elem => elem.getText()).then(titles => {
var position = titles.indexOf("testName") + 1;
return "//div[#id='Tasks_Tab'][" + position + "]";
});
}
Here you go, I cleaned it up a bit. This will actually return an error if one is experienced in the nested promises:
var findCreatedTask = function() {
var Promise = require('bluebird');
var createdTask;
return driver.findElements(By.xpath("//div[#id='Tasks_Tab']"))
.then(function(tasks) {
return Promise.map(tasks, function(task){
return driver.findElement(By.xpath("//div[#id='Tasks_Tab'][" + index + "]//div[#class='task-title']")).getText()
}).then(function(taskTitles){
for (let i = 0; i < taskTitles.length; i++){
if(taskTitles[i] === 'testName'){
createdTask = "//div[#id='Tasks_Tab'][" + i + "]";
return createdTask;
}
}
});
});
};
You call it using
findCreatedTask.then(function(res){
//do your thing
}).catch(function(err){
console.error(err.stack);
});
You will not be able to return the value that you want from this function because when this function returns, the value is not defined yet.
This is not a problem that you try to return the value in the wrong place, but that you try to access it at the wrong time.
You have two options: you can either return a promise from this function, or this function can take a callback that would be called when the value is available.
Examples
This is not tested but should give you an idea on how to think about it.
Promise
Version with promise:
var findCreatedTask = function (callback) {
var createdTask;
return new Promise(function (resolve, reject) {
driver.findElements(By.xpath("//div[#id='Tasks_Tab']")).then(function(tasks) {
for (let index = 1; index <= tasks.length && !createdTask; index++) {
driver.findElement(By.xpath("//div[#id='Tasks_Tab'][" + index + "]//div[#class='task-title']")).getText().then(function(taskTitle) {
if (taskTitle == "testName") {
createdTask = "//div[#id='Tasks_Tab'][" + index + "]";
resolve(createdTask);
}
});
}
});
});
};
and then you call it with:
findCreatedTask().then(function (createdTask) {
// you have your createdTask here
});
Callback
Version with callback:
var findCreatedTask = function (callback) {
var createdTask;
driver.findElements(By.xpath("//div[#id='Tasks_Tab']")).then(function(tasks) {
for (let index = 1; index <= tasks.length && !createdTask; index++) {
driver.findElement(By.xpath("//div[#id='Tasks_Tab'][" + index + "]//div[#class='task-title']")).getText().then(function(taskTitle) {
if (taskTitle == "testName") {
createdTask = "//div[#id='Tasks_Tab'][" + index + "]";
callback(null, createdTask);
}
});
}
});
};
and then you call it with:
findCreatedTask(function (err, createdTask) {
// you have your createdTask here
});
More info
You can read some other answers that explain how promises and callbacks work if you're interested to know ore about it:
A detailed explanation on how to use callbacks and promises
Explanation on how to use promises in complex request handlers
An explanation of what a promise really is, on the example of AJAX requests
An explanation of callbacks, promises and how to access data returned asynchronously

Mongoose two request depending each other

I have images model and users model.
every image has a user_id field of a user and I want to get the picture of the user and name, add it to the image object and return an array of images.
When I am trying to add author_image field to ONE image I don't have any errors,
But when I am looping over all the images the app crashes the output is that imagesData is undefined as well as userData.
I tried using promises but again I get an error.
What is the best way I can do that without the undefined error?
router.route('/images/all')
.get(function(req,res){
var response = {};
var imagesData = {};
images.find({}).lean().exec(function(err,data){
// console.log(data);
imagesData = data;
if (!err) {
for (var i = 0; i < imagesData.length; i++) {
users.find(({'_id': imagesData[i].user_id}),function(err,userData){
console.log(userData);
imagesData[i].author_pic = userData[0].profile_image;
});
}
}
res.json(imagesData);
});
});
What you missed out is that find operation is not a synchronous operation. So all your find operation immediately move on to the next line.
Although there are multiple ways to handle such situation, I tend to use promises (Q library).
The code would look like this
var Q = require('q');
var defer = Q.defer();
images.find({}).lean().exec(function (err, data) {
// console.log(data);
imagesData = data;
var promiseArr = [];
if (!err) {
for (var i = 0; i < imagesData.length; i++) {
var innerDefer = Q.defer();
users.find(({'_id': imagesData[i].user_id}), function (err, userData) {
console.log(userData);
defer.resolve(userData[0].profile_image);
});
promiseArr.push(innerDefer);
}
}
Q.all(promiseArr).then(function (results) {
for (var i = 0; i < imagesData.length; i++) {
if (Q.isPromise(results[i])) {
results[i] = results[i].valueOf();
}
imagesData[i].author_pic = results[i];
}
res.json(imagesData);
})
});
In this case I am using the Q.all method which basically waits for all the find to finish, and executes only then.

Resources