I must connect to a LDAP server and find user based on a specific ID.
I've decided to use ldapjs module.
I managed to create my client and bind him so I can search for an user with success.
My problem is, as I only use async/await, that I don't understant how to handle error in callbacks... For example with this simple code from ldapjs library :
public static async search(searchOptions: SearchOptions) {
LdapService.bind()
LdapService.getClient()?.search('ou=*****,ou=***,dc=****,dc=****', searchOptions, function (err, res) {
ifError(err)
res.on('searchEntry', function (entry) {
// ----------------------
// an error came from here
throw new Error('test')
// ----------------------
console.log('entry: ' + JSON.stringify(entry.object));
});
res.on('searchReference', function (referral) {
console.log('referral: ' + referral.uris.join());
});
res.on('error', function (err) {
console.error('error: ' + err.message);
});
res.on('end', function (result) {
console.log('status: ' + result?.status);
});
})
}
LdapService.getClient() is a singleton method that return the result of createClient -> works fine
LdapService.bind() is a method that just bind with the server with correct credentials -> works fine
I just can't manage to handle my error "test"... How am I supposed to handle it?
Is the search method really async?
Can I do it the async/await way? :P
PS: the DN string ("ou=,ou=,dc=,dc=") is hidden due to security reasons and the code works great without throwing an error ;)
For anyone passing here and struggling with callback like me, here the "fix" I found :
public static async search(searchOptions: SearchOptions) {
// wrapping the all thing in a Promise that can be in a try/catch -> error will be handled there
return await new Promise((resolve, reject) => {
LdapService.getClient()?.search('ou=****,ou=****,dc=****,dc=****', searchOptions, function (err, res) {
if (err) {
// handle connection error I guess ?
reject(err)
}
res.on('searchEntry', function (entry) {
try {
// -------------
// Same test as before
throw new Error('test')
// -------------
resolve(JSON.stringify(entry.object))
} catch (err) {
// error must be catched to call reject method !
reject(err)
}
});
res.on('error', function (err) {
// handle API error when looking for the specific ID I guess ?
reject(err)
});
})
})
}
What solved my problem? Wrapping the all thing in a Promise.
All the "res.on" methods before were something like Promise listeners. So the all method search (the one inside mine) was a sort of asynchronous call.
Now I can call resolve/reject methods to return data and error.
Also, I can call my static search method the async/await way.
When you're not familiar with callbacks... ^^"
Related
I am using Node.js and have a database call being performed with a promise. I setup a '.then/.catch' to handle the result of the promise. That part all seems to work without any issue. After I do some processing of the data returned from the database I am attempting to 'redirect' to another route within the '.then' statement. This re-direction seems to be causing a problem with the '.catch' statement. I have no error issues if I remove the 'res.redirect'...is there a workaround? Any suggestions appreciated.
code:
const db = require('./routes/queries');
//**Query PostGres database...
db.getMembers()
.then(function(value) {
console.log('Async success!', value);
//do some processing on the data returned from the promise call here...
//if I insert a redirect here...such as "res.redirect('/_members');"...
//I get a "Caught an error! ReferenceError: res is not defined" message...?
//evidently the redirect fires the '.catch' statement below...why?
})
.catch(function(err) {
console.log('Caught an error!', err);
});
I recommend providing a reusable promise in case you need to find your members list elsewhere in your Express app
const db = require('./routes/queries');
const findMembers = () => {
return db.getMembers()
.then(members => {
return members
})
.catch(err => {
console.log('Caught an error!', err);
});
}
//inside your express app
app.get("/some_route", (req, res) => {
return findMembers()
.then(members => {
//do something with your members list
res.redirect("/redirect_route")
})
})
I have a mysql statement that creates an entry, it has a .then function and a .catch function, but when the following error occurs:
TypeError('Bind parameters must not contain undefined. To pass SQL NULL specify JS null');
the server crashes instead of answering a 500 like defined in the .catch function
Note: I'm using the mysql2 library from npm with promises (require('mysql2/promise');)
Here's the code that calls it (req.params.account_name is undefined):
const CREATE_ACCOUNT_STATEMENT =
'INSERT INTO `Accounts` (`account_token`, `account_name`) VALUES (?, ?)'
try {
mysqlConnectionPool.execute(CREATE_ACCOUNT_STATEMENT, [
account_token, account_name
])
.then(() => {
res.end(JSON.stringify({ token: account_token }))
})
.catch((e) => {
debug(1, "error while trying to create account:", e)
res.status(500).end("Internal Server Error")
})
} catch(e) {
debug(1, "error while trying to create account:", e)
res.status(500).end("Internal Server Error")
}
Actually, #Quentine was close to the right thing...
It is "sort of" a bug in mysql2,
i use sort-of because https://github.com/sidorares/node-mysql2/issues/902 suggests the development team of mysql2 is o.k. with it.
it is an issue with the way mysql2.pool passes the call to the created connection, which does not pass the exception to the wrapping promise.
I ended up making my own wrapping function to create the connection + call execute wrapped in proper promise handling.
import mysql = require('mysql2');
private async queryDB(query:string, useExecute: boolean = false, ...args:any[]) : Promise<any[]>
{
return new Promise<any[]>((resolve, reject)=>{
for(var i = 0; i < args.length; ++i)
{
if(args[i]===undefined)
args[i] = null;
}
this.dbPool.getConnection((err, conn)=>{
if(err){
reject(err);
return;
}
let cb = function(err: mysql.QueryError, results: any[], fields: mysql.FieldPacket[]) {
conn.release();
if(err)
{
reject(err);
return;
}
resolve(results);
}
if(useExecute)
conn.execute(query, args, cb);
else
conn.query(query, args, cb);
});
});
}
mysqlConnectionPool.execute is throwing the exception before creating a promise.
i.e. the exception is not thrown from within the promise.
To catch it you would need to try {} catch (e) {} around the call to mysqlConnectionPool.execute.
Well,
I'm guessing that you are using the standard mysql package which it seems not supporting Promises, instead, it accepts a standard node callback function(err, results, fields) {} as an argument of the execute method.
So since you haven't defined a valid callback the script will just throw an exception.
Please take into consideration that similar questions have been asked on SO and I went through most of them.
I am making a RESTful service that needs querying the DB to get the data. I wrote the code that queries the database correctly but does returns undefined all the time. The code is here:
function returnAll(){
ModuleDBService.find({},function(err,data){
if(err){
console.log('Error occured while retrieving the documents!');
}
return data;
});
}
I was exporting the module using:
module.exports = {
getAll:returnAll
};
After digging SO a lot, I discovered that I will need to use callback to get the data. I went through many examples and tried to apply a similar technique to my code, the modified code looked like this:
function getAllFromDatabase(callback){
ModuleDBService.find({},function(err,data){
if(err){
console.log('Error occured while retrieving the documents!');
}
callback(returnAll(data));
});
}
function returnAll(data){ return data;}
and then returning it in the similar fashion as above.
But now I am getting error that ModuleDAO.getAll is not a function (I am using var ModuleDAO = require('path to the database service').
I tried many variations of the code, went through a couple of videos on YouTube, all of them either lead to returning undefined, or return to the above stated error. If anyone could fix the code and throw light on this whole callback thing (Or could provide solid documentation to understand it), it'll be a great help.
Thanks.
EDIT: After all the extremely helpful answers, here is a summary:
Callbacks cannot return data, pass the function (the callback function) that you want your program to call with the data. In my case, it was my router returning the data.
Here is the corrected code:
function returnAll(callback) {
ModuleDBService.find({}, function (err, data) {
if (err) {
console.log("Error while retrieving the document!")
callback(null);
}
callback(data);
});
}
I used this code in my router as:
mainAPIRouter.post('/api/module', function (req, res) {
try {
moduleDAO.getAll(function(data){
res.status(200);
res.json(data);
});
} catch (error) {
res.status(500);
return res.send("Invalid request");
}
});
Thanks to all those who helped! :)
You are close. You don't need the returnAll() function, and you need to export getAllFromDatabase and pass a callback to it:
function getAllFromDatabase(callback){
ModuleDBService.find({},function(err,data){
if(err) {
console.log('Error occured while retrieving the documents!');
}
callback(data);
});
}
module.exports = {
getAllFromDatabase: getAllFromDatabase
};
Then, when you want to use it, you need a callback function:
dataModule.getAllFromDatabase(callbackHandler);
function callbackHandler(dataFromDatabase) {
// this function will be executed when ModuleDBService executes the callback
console.log(dataFromDatabase);
}
A small detail: if err is Truthy, you should not execute the callback:
if(err) {
console.log('Error occured while retrieving the documents!');
} else {
callback(data);
}
You want to simply call the callback() with the data you need as an argument. You are making things much more complicated by passing another function into the callback. Try something like:
function returnAll(callback) {
ModuleDBService.find({}, function(err, data) {
if (err) return callback(err)
callback(null, data);
});
}
returnAll(function(err, data)) {
// it's customary for callbacks to take an error as their first argument
if (err) {
console.log('Error occured while retrieving the documents!');
} else {
// use data here!!
}
}
As previous answers mentioned, you can use a callback. You can also use a promise if you prefer:
function returnAll(){
return new Promise(function(resolve, reject) {
ModuleDBService.find({},function(err,data){
if(err){
console.log('Error occured while retrieving the documents!');
reject(err);
}
resolve(data);
});
});
}
You would then use something like this to access it:
returnAll()
.then(data=> {console.log(data); })
.catch(err=> { console.log(err); });
*Edit: since you want to use a callback, thought I'd add my $0.02 there as well. The most streamlined approach would be to just use the callback you're passing in with the ModuleDBService.find, without the ad-hoc function. But it's good to verify that callback actually is a function, and if not to make it one...makes your code more fault-tolerant.
function returnAll(cb){
if(typeof cb!=='function') cb = function() {};
ModuleDBService.find({},cb);
}
In my node js program, I look into my mongoose database, and find and return the values in that collection - there is only one value.
var myValueX;
myCollection.find(function(err, post) {
if (err) {
console.log('Error ' + err)
} else {
myValueX = post[0].valuex;
}
});
console.log('Have access here' + myValueX);
Now, I want to be able to use myValueX outside this find method. How can I do this?
When I try the console.log above, I get undefined back - is this possible to achieve
To access myValueX after it had been assigned in find's callback, you have two options, the first (naturally) is inside the callback itself, or inside a function called by the callback to which you send myValueX as an argument.
The better solution in my opinion is to use promises.
A simple promise-using solution is as follows:
function findPromise(collection) {
return new Promise((resovle, reject) => {
collection.find((err, post) => {
if (err)
return reject(err)
// if all you want from post is valuex
// otherwise you can send the whole post
resolve(post[0].valuex)
})
})
}
findPromise(collection)
.then((valueX) => {
// you can access valuex here
// and manipulate it anyway you want
// after it's been sent from the `findPromise`
console.log("valueX: ", valueX)
})
.catch((err) => {
console.log("An error occurred. Error: ", err)
})
Ive been playing around with mongodb in node.js. I have made a basic collection with some data (i know its there ive checked). When I try to run a find() on the collection it returns undefined. I dont know why this is. The code is below:
function get_accounts(){
var MongoClient = mongodb.MongoClient;
var url = "url";
MongoClient.connect(url, function (err, db) {
if (err) {
console.log('Unable to connect to the mongoDB server. Error:', err);
} else {
//HURRAY!! We are connected. :)
console.log('Connection established to database');
var collection = db.collection('accounts');
collection.find().toArray(function(err, docs) {
console.log("Printing docs from Array")
docs.forEach(function(doc) {
console.log("Doc from Array ");
console.dir(doc);
});
});
console.log("mission complete");
}
db.close();
}
);
}
If you know why this is happening i would like to hear your thoughts. thanks! The database is a mongolab hosted database if that makes any difference.
You are getting an undefined value because of the asynchronous nature of node.js, nowhere in your code exists logic that tells the console.log statement to wait until the find() statement finishes before it prints out the documents. You have to understand the concept of callbacks in Node.js. There are a few problems here, though, that you could fix. A lot of people getting started with node have the tendency to nest lots of anonymous functions, creating the dreaded "pyramid of doom" or callback hell. By breaking out some functions and naming them, you can make it a lot cleaner and easier to follow:
var MongoClient = require("mongodb").MongoClient
// move connecting to mongo logic into a function to avoid the "pyramid of doom"
function getConnection(cb) {
MongoClient.connect("your-mongo-url", function(err, db) {
if (err) return cb(err);
var accounts = db.collection("accounts");
cb(null, accounts);
})
}
// list all of the documents by passing an empty selector.
// This returns a 'cursor' which allows you to walk through the documents
function readAll(collection, cb) {
collection.find({}, cb);
}
function printAccount(account) {
// make sure you found your account!
if (!account) {
console.log("Couldn't find the account you asked for!");
}
console.log("Account from Array "+ account);
}
// the each method allows you to walk through the result set,
// notice the callback, as every time the callback
// is called, there is another chance of an error
function printAccounts(accounts, cb) {
accounts.each(function(err, account) {
if (err) return cb(err);
printAccount(account);
});
}
function get_accounts(cb) {
getConnection(function(err, collection) {
if (err) return cb(err);
// need to make sure to close the database, otherwise the process
// won't stop
function processAccounts(err, accounts) {
if (err) return cb(err);
// the callback to each is called for every result,
// once it returns a null, you know
// the result set is done
accounts.each(function(err, account) {
if (err) return cb(err)
if (hero) {
printAccount(account);
} else {
collection.db.close();
cb();
}
})
}
readAll(collection, processAccounts);
})
}
// Call the get_accounts function
get_accounts(function(err) {
if (err) {
console.log("had an error!", err);
process.exit(1);
}
});
You might have to add an empty JSON object inside the find.
collection.find({})
Documentation can be found here.
You must enter this code in an async function and you will be fine here data is the your desired value and you must use promises to not make your code look messy.
var accountCollection = db.collection('accounts);
let data = await accountCollection.find().toArray.then(data=>data).catch(err=>err);