I have a problem with Express response and Promise.
My script call a MongoDB Database to get informations from the server. I use Promise to send the result only when my server answered me.
The problem is in my route. I get the response correctly, but the
res.send returns nothing. I can't figure why.
Let's review my code. I've truncated some of the files, as it is not important code.
I have first a server.js file with my router declaration :
let router = express.Router();
app.use('/mongo', MongoRouter);
Then here is my MongoRouter.js file :
import { Router } from 'express';
import { MongoController } from '../Controllers/MongoController';
class MongoRouter {
constructor() {
this.router = Router();
this.controller = new MongoController();
this.routes();
}
routes() {
let obj = this;
this.router.get('/global', (req, res) => {
let promise = obj.controller.globalInfos();
promise.then((value) => {
console.log('Resolve OK. Showing data');
console.log(value);
res.send(value);
}).catch((error) => {
res.status(500).send(error.toString());
});
});
this.router.get('*', (req, res) => {
res.json({
data: 'Invalid route'
});
});
}
}
export default new MongoRouter().router;
And the function globalInfos() from my MongoController :
globalInfos() {
var status = '';
var globalInfos = new Array();
var internalServerReached = 0;
var dbCommand = {'serverStatus': 1};
let obj = this;
return new Promise((resolve, reject) => {
obj.cnxStrings.forEach(element => {
MongoClient.connect(element['cnxString'], (err, db) => {
if (!err) {
if (element['replica'] == true) {
dbCommand = {'replSetGetStatus':1};
}
db.command(dbCommand, (err, results) => {
if (!err) {
status = results;
} else {
status = 'Status unknown';
}
globalInfos[element['name']] = status;
internalServerReached++;
db.close();
if (internalServerReached >= obj.cnxStrings.length) {
console.log('Waiting 3s for the resolve');
setTimeout(() => {
resolve(globalInfos);
},3000);
}
});
} else {
status = 'Unreachable';
}
});
});
});
}
When i go to http://localhost:3000/mongo my function is running, the resolve is called after 3sec, my data are ok in the console (in the .then() callback), but the res.send(..) show an empty array []
Thanks!
Related
I've been reading about async and await in JS and tried to implement them in my code (which I totally messed up).
Here is my JS.
var express = require('express');
var router = express.Router();
var jsforce = require('jsforce');
const SEC_TOKEN = 'SEC_TOKEN';
const USER_ID = 'USER_ID';
const PASSWORD = 'PWD';
const { default: axios } = require('axios');
router.get("/", async (req, res, next) => {
await initConnect;
await soqlData;
await slackPostTest;
});
initConnect = async () => {
var conn = new jsforce.Connection({
loginUrl: 'https://login.salesforce.com'
});
await conn.login(USER_ID, PASSWORD + SEC_TOKEN, (err, userInfo) => {
if (err)
console.log(err);
else {
console.log(userInfo.Id);
}
});
}
soqlData = async () => {
await conn.query('Select Id, Name from Account LIMIT 1', (err, data) => {
if (err)
console.log(err);
else
return data.records[0];
})
}
slackPostTest = async () => {
await axios.post('SLACK_WEBHOOK', {
"text": "soqlRes"
})
}
module.exports = router;
What I am trying to achieve?
Initialize my connection by passing in SEC_TOKEN, USER_ID, PASSWORD to my initConnect function this will give me a connection (conn).
Use this conn and query my salesforce instance and get some other data.
post some message(currently irrelevant, but will hook up with the above response later) to my slack endpoint.
Also can someone please give me a little detailed explanation of the solution (in terms of async/await)?
Thanks
Assuming everything else about your JsForce API usage was correct (I have no experience with it, so I can't say), here's how to promisify those callback-based APIs and call them.
var express = require("express");
var router = express.Router();
var jsforce = require("jsforce");
const SEC_TOKEN = "SEC_TOKEN";
const USER_ID = "USER_ID";
const PASSWORD = "PWD";
const { default: axios } = require("axios");
router.get("/", async (req, res, next) => {
const { conn, userInfo } = await initConnect();
const data = await soqlData(
conn,
"Select Id, Name from Account LIMIT 1",
);
await slackPostTest(data.records[0]);
});
function initConnect() {
const conn = new jsforce.Connection({
loginUrl: "https://login.salesforce.com",
});
return new Promise((resolve, reject) => {
conn.login(USER_ID, PASSWORD + SEC_TOKEN, (err, userInfo) => {
if (err) return reject(err);
resolve({ conn, userInfo });
});
});
}
function soqlData(conn, query) {
return new Promise((resolve, reject) => {
conn.query(query, (err, data) => {
if (err) return reject(err);
resolve(data);
});
});
}
function slackPostTest(soqlRes) {
return axios.post("SLACK_WEBHOOK", {
text: soqlRes,
});
}
module.exports = router;
I currently have a script that checks for an incoming email (in a mailbox) every 30 seconds, using a recursion.
The package I'm using for this testing is imap-simple.
The below script currently does this as required;
var imaps = require('imap-simple');
const { connect } = require('net');
var config = {
imap: {
user: 'qatestspecialist#outlook.com',
password: 'specialistQa',
host: 'imap-mail.outlook.com',
port: 993,
tls: true,
authTimeout: 30000
}
};
module.exports = {
'delete any existing emails...': function () {
imaps.connect(config).then(function (connection) {
connection.openBox('INBOX').then(function () {
var searchCriteria = ['ALL'];
var fetchOptions = { bodies: ['TEXT'], struct: true };
return connection.search(searchCriteria, fetchOptions);
//Loop over each message
}).then(function (messages) {
let taskList = messages.map(function (message) {
return new Promise((res, rej) => {
var parts = imaps.getParts(message.attributes.struct);
parts.map(function (part) {
return connection.getPartData(message, part)
.then(function (partData) {
//Display e-mail body
if (part.disposition == null && part.encoding != "base64"){
console.log(partData);
}
//Mark message for deletion
connection.addFlags(message.attributes.uid, "\Deleted", (err) => {
if (err){
console.log('Problem marking message for deletion');
rej(err);
}
res(); //Final resolve
});
});
});
});
});
return Promise.all(taskList).then(() => {
connection.imap.closeBox(true, (err) => { //Pass in false to avoid delete-flagged messages being removed
if (err){
console.log(err);
}
});
connection.end();
});
});
});
},
'send email to seller and wait for mailbox notification': function (browser) {
// script to send an email to the mailbox...
},
'get new email info': function(browser) {
const createPromise = ms => new Promise((resolve, reject) => {
setTimeout(() => resolve(ms), ms)
});
function findUnseenEmails(connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = ['UNSEEN'];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
markSeen: false
};
return connection.search(searchCriteria, fetchOptions).then(function (results) {
var subjects = results.map(function (res) {
return res.parts.filter(function (part) {
return part.which === 'HEADER';
})
[0].body.subject[0];
});
return subjects.length > 0 ? subjects : createPromise(30000).then(function() { return findUnseenEmails(connection);
});
});
});
}
imaps.connect(config).then(function (connection) {
return findUnseenEmails(connection)
})
.then((subjects) => console.log(JSON.stringify(subjects)));
},
'Closing the browser': function (browser) {
browser.browserEnd();
}
};
This waits for an email and then displays the email 'header'.
However, the imap connection does not close, and stays open which is stopping my test suite from completing as the associated test never actually finishes.
I've tried adding the imap-simple command connection.end() in several places after the
imaps.connect(config).then(function (connection) {
return findUnseenEmails(connection)
})
part of the script, but it doesn't work.
So I'm just wondering if anyone knows where I should be adding this connection.end() command in order for the connection to be closed once an email has been received?
Any help would be greatly appreciated.
This has now been resolved in another post, using the following;
if (subjects.length > 0) {
connection.end();
return subjects;
} else {
return createPromise(5000).then(function() { return findUnseenEmails(connection)});
}
I am working an Express web application that runs JavaScript scraping code whenever the page is first loaded.
Here is the node web scraping code (scrape.js):
const request = require('request-promise');
const cheerio = require('cheerio');
const fs = require('fs');
const data = require('../public/state_data.json');
const cases_data = require('../public/cases_data.json');
// retrieve wikipeida page
request('https://en.wikipedia.org/wiki/2020_coronavirus_pandemic_in_the_United_States', (error, response, html) => {
if(!error && response.statusCode == 200) {
// create cheerio scraper
const $ = cheerio.load(html);
// find, and loop through all the rows in the table
var rows = $('.wikitable').find('tr');
for(var i = 3; i < 59; i++) {
// scrape state name and cases from table
var state = $(rows[i]).children('th:nth-child(2)').text().split("\n");
var cases = parseInt($(rows[i]).children('td').html().replace(",", ""));
// update state_data.json file w/ proper cases and per capita
for(var j = 0; j < data.length; j++) {
if(data[j].state === state[0]) {
// push new data to cases_data.json
cases_data.push({
state: state[0],
latitude: data[j].latitude,
longitude: data[j].longitude,
cases: cases,
percapita: (cases / data[j].population)
});
// write to new cases_data.json file w/ state name, cases and calculated per capita
fs.writeFile('../public/cases_data.json', JSON.stringify(cases_data, null, 2), function(err) {
if (err) throw err;
});
}
}
}
} else {
console.log('request error')
}
});
And here is the express app (app.js):
const express = require('express');
const app = express();
const port = 3000;
const scrape = require('./scrape.js');
app.get('/', (req, res) => {
scrape();
res.render('index');
})
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
Right now when I run 'node app.js' I get this as an error:
TypeError: scrape is not a function
I've tried wrapping scrape.js in a function to no avail. Any ideas?
Fix / Solution:
I had to export the request function as shown in code below:
module.exports = () => {
request('https://en.wikipedia.org/wiki/2020_coronavirus_pandemic_in_the_United_States', (error, response, html)) => {
... remaining code ...
}
}
You need to export a function from scrape.js, Like:
const request = require('request-promise');
const cheerio = require('cheerio');
const fs = require('fs');
const data = require('../public/state_data.json');
const cases_data = require('../public/cases_data.json');
module.export = () => {
// retrieve wikipeida page
request('https://en.wikipedia.org/wiki/2020_coronavirus_pandemic_in_the_United_States', (error, response, html) => {
...
});
};
Also, I suggest you to handle your async code with a callback or a promise, like:
With promise:
// scrape.js
module.export = () => {
return new Promise((resolve, reject) => {
// retrieve wikipeida page
request('https://en.wikipedia.org/wiki/2020_coronavirus_pandemic_in_the_United_States', (error, response, html) => {
if(!error && response.statusCode == 200) {
...
resolve();
} else {
console.log('request error');
reject(error);
}
});
});
};
// app.js
app.get('/', (req, res) => {
scrape()
.then(() => {
res.render('index');
})
.catch((error) => {
// response with an error
});
});
With callback:
// scrape.js
module.export = (cb) => {
// retrieve wikipeida page
request('https://en.wikipedia.org/wiki/2020_coronavirus_pandemic_in_the_United_States', (error, response, html) => {
if(!error && response.statusCode == 200) {
...
cb(null);
} else {
console.log('request error');
cb(error);
}
});
};
// app.js
app.get('/', (req, res) => {
scrape((error) => {
if (error) {
// response with an error;
return;
}
res.render('index');
});
})
Export the function:
// scrape.js
module.exports = () => {
request('https://en.wikipedia.org/wiki/2020_coronavirus_pandemic_in_the_United_States', (error, response, html) => {
// rest of function
}
}
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
I'm working in a simple API Key authentication, I just want to verify the given key against the user provied key.
I have a seperate file with the function querying the database, and returning true/false and the user object.
But in my route.js file, the return object is undefined even tough in my auth.js file it isn't.
I tried making the the function in router.get an async function using express-promise-router and making the function an await return var user = await auth.verify(req.params.uid, req.get("token")) but I don't realy know how async works.
router.js
[...]
router.get('/list/:uid', function(req, res) {
var user = auth.verify(req.params.uid, req.get("token"))
console.log("User: " + user) // <-- Undefined
if (user.status) {
res.send("Success")
} else {
res.status(403)
res.json({status: 403, error: "Unkown User / Token"})
}
})
[...]
auth.js
var db = require('./db')
var ObjectId = require('mongodb').ObjectId;
module.exports = {
verify: (uid, key) => {
try {
var collection = db.get().collection('users')
const obj_id = new ObjectId(uid)
const query = { _id: obj_id }
collection.find(query).limit(1).toArray(function(err, user) {
var status = 0;
var usr = {};
if (err) {throw err}else{status=1}
if (user.length <= 0) {throw "NotExistingExc"; status = 0}else{
usr = user[0];
if (key != usr.api) status = 0
}
var returnObj = {
status: status,
user: usr
} /* --> Is {
status: 1,
user: {
_id: d47a2b30b3d2770606942bf0,
name: 'Sh4dow',
groups: [ 0 ],
api: 'YWFiMDI1MGE4NjAyZTg0MWE3N2U0M2I1NzEzZGE1YjE='
}
}
*/
return returnObj;
})
} catch (e) {
console.error(e)
return {
status: 0,
user: {},
error: e
}
}
}
}
db.js (Idk if needed)
var MongoClient = require('mongodb').MongoClient
var state = {
db: null,
}
exports.connect = function(url, done) {
if (state.db) return done()
MongoClient.connect(url, { useNewUrlParser: true }, function(err, db) {
if (err) return done(err)
state.db = db
done()
})
}
exports.get = function() {
return state.db.db("database")
}
exports.close = function(done) {
if (state.db) {
state.db.close(function(err, result) {
state.db = null
state.mode = null
done(err)
})
}
}
I want to have the returnObjin auth.js in the router.get of my route.js file.
Make auth.verify return a Promise which we can then await for it inside router, You can just make the callback async no need for express-promise-router
router.get('/list/:uid', async function(req, res) {
try {
var user = await auth.verify(req.params.uid, req.get("token"))
console.log("User: " + user)
if (user.status) {
res.send("Success")
} else {
res.status(403).json({status: 403, error: "Unkown User / Token"})
}
} catch (e) {
console.error(e)
res.status(/* */).json(/* */)
}
})
auth
module.exports = {
verify: (uid, key) => new Promise((resolve, reject) => {
var collection = db.get().collection('users')
const obj_id = new ObjectId(uid)
const query = { _id: obj_id }
collection.find(query).limit(1).toArray(function(err, user) {
var status = 0;
var usr = {};
if (err) {
reject(err)
return
} else {
status = 1
}
if (user.length <= 0) {
reject(new Error("NotExistingExc"))
return
} else {
usr = user[0]
if (key != usr.api) status = 0
}
var returnObj = {
status: status,
user: usr
}
resolve(returnObj);
})
}
}
In short, the reason you get undefined is because the code in auth.js is asyncronous. But you're really close. The toArray method in MongoDB returns a promise, so you need to make sure you return that promise and then use it in the router correctly.
In auth.js, make sure verify returns a promise - just add return!
return collection.find(query).limit(1).toArray(...)
And then, change your usage of the verify to the async/await you originally tried:
router.get('/list/:uid', async function(req, res) {
var user = await auth.verify(req.params.uid, req.get("token"))
// More code here...
})
I've got a method in a class which does query an ActiveDirectory.
Therefore I'm using 'activedirectory2' npm package.
I successfully authenticated and successfully logged my result to console.
Now that I have instanciated my class and have tried to call the method, I'm not abled to get a non-empty result.
I tried it with getters/setters to make the _result value available after instaciating the class.
I tried to solve my issue with research on asynchronous calls, but obviously wasn't able to ask the right question.
class Activedirectory
var ActiveDirectory = require("activedirectory2");
class AuthenticateWithLDAP {
constructor(user, password){
this._result = [];
this.user = user;
this.password = password;
this.config = {
url: "ldaps://someldap",
baseDN: "somebasdn",
username: this.user,
password: this.password,
filter: 'somefilter',
}
this.ad = new ActiveDirectory(this.config);
}
//Auth Method
auth() {
var result = this._result;
this.config.entryParser = function(entry,raw,callback){
if(entry.hasOwnProperty('info')) {
result.push(entry.info);
this._result = result;
}
callback(entry);
}
this.ad.authenticate(config.username, config.password, (err,auth)=>{
if (err) {
//some error handling
}
if (auth) {
this.ad.find(config,async (err, userDetails) => {
var result = this._result;
{
if (err) {
//some error handling
}
if(!userDetails) {
console.log("No users found.");
} else {
this._result = result[0]; //I want this result!
console.log('result: ', this._result);
return await this._result;
}
}
})
} else {
console.log("Authentication failed!");
}
});
}
//getter/setter
get result(){
return this._result;
}
set result(value) {
this._result.push(value);
}
}
module.exports = AuthenticateWithLDAP;
route module
const express = require('express');
const AuthwithLDAP = require('AuthenticateWithLDAP');
const router = express.Router();
router.post('/', async (req,res,next) => {
let x = async ()=> {
authwithldap = new AuthwithLDAP(req.body.user,req.body.password);
return await authwithldap.auth();
}
x().then((res)=>{
console.log('res: ', res); //always []
})
})
I expected to be able to use the _result value of AuthenticateWithLDAP class in my router.post method handler.
Actually i only get [] (empty array) in router.post.
Could you please tell me how to alter the value _result in a way, so that the instance of the class knows it and can use it outside the class itself.
Thank you very much.
Micha
I am not 100% sure but I think this should work.
In your code you cant return the result because the return is in a callback.
There are to ways to fix that.
Pass a callback to the auth() method (This is bad since callbacks suck)
Return a promise and that resolves to the result
I've decided to go for promises.
var ActiveDirectory = require("activedirectory2");
class AuthenticateWithLDAP {
constructor(user, password){
this._result = [];
this.user = user;
this.password = password;
this.config = {
url: "ldaps://someldap",
baseDN: "somebasdn",
username: this.user,
password: this.password,
filter: 'somefilter',
}
this.ad = new ActiveDirectory(this.config);
}
//Auth Method
auth() {
return new Promise((resolve, reject) => {
this.ad.authenticate(config.username, config.password, (err,auth)=>{
if (err) {
//Call reject here
}
if (auth) {
this.ad.find(config,async (err, userDetails) => {
var result = this._result;
{
if (err) {
//some error handling
}
if(!userDetails) {
console.log("No users found.");
} else {
this._result = result[0]; //I want this result!
resolve(await this._result);
}
}
})
} else {
console.log("Authentication failed!");
}
});
});
}
}
module.exports = AuthenticateWithLDAP;
const express = require('express');
const AuthwithLDAP = require('AuthenticateWithLDAP');
const router = express.Router();
router.post('/', async (req,res,next) => {
/* This code can be simplifed
let x = async () => {
authwithldap = new AuthwithLDAP(req.body.user,req.body.password);
return await authwithldap.auth();
}
x().then((res)=>{
console.log('res: ', res); //always []
})
*/
(async () => {
authwithldap = new AuthwithLDAP(req.body.user,req.body.password);
var res = await authwithldap.auth();
console.log('res: ', res);
})();
})
Could you try to add syntax "await" like this?
await x().then((res)=>{
console.log('res: ', res); //always []
})
As your "x" method is in async mode, maybe you have to wait for the Promise to be resolved...