Node.js mysql in function returns undefined - node.js

Here's my code
function getprefix(guildid) {
con.connect(function(err) {
con.query('SELECT * from ' + configcontent.guilds_table_name + ' WHERE guild_id=' + String(guildid), function(err, result) {
if (err) {
throw err;
} else {
return result[0].prefix;
}
});
});
}
I wanted to make this function to return one value from one column (from first result), but it returns undefined. When i tried to write it on screen (using console.log(result[0].prefix);) it works.
Please help. Sorry for bad description or english. Its my first question on StackOverflow

Your function is asynchronous, so it immediately returns result, now it returns undefined. Value from DB returned later, and passed to callback. You need to use promises(preferred)/callbacks to return async results from your function, something like this:
function getprefix(guildid) {
return new Promise((resolve, reject) => {
con.connect(function (err) {
con.query('SELECT * from ' + configcontent.guilds_table_name + ' WHERE guild_id=' + String(guildid), function (err, result) {
if (err) {
// throw err;
// instead of throwing we passing error to result promise
reject(err)
} else {
// return result[0].prefix;
// instead of return we pass result to promise
resolve(result[0].prefix)
}
});
});
}
}
so usage will look like:
getprefix(guildid)
.then(prefix => console.log(prefix))
.catch(e => console.err('Unable to get prefix', e))
it would be great to check the different ways to handle async programming in JS with callbacks/promises/async awaits in details.

You can't return value from a callback.
Before you make your query, connect to the db. Don't use the callback function for that, cuz making a query supposed to be made with con.query
function getprefix(guildid) {
con.connect();
con.query('SELECT * from ' + configcontent.guilds_table_name + ' WHERE guild_id=' + String(guildid), function(err, result) {
if (err) {
throw err;
} else {
// do stuff with 'results'
}
});
EDIT
You can use async/await like this to get access to the return value, but you have to wrap your code in an IIFE (Immediately Invoked Function Expression) or use top level await, but it's only available in the latest version of Node.
Note, that I only used IIFE, because I don't have any other function or logic where I could call my query. If you call it in another function, make it async.
// Top level action
(async () => {
const output = await makeQuery();
console.log(output);
})();
// Query
function makeQuery() {
return new Promise((resolve, reject) =>
db.query("SELECT * FROM users", (err, res) => {
if (err) reject(err);
else resolve(res);
})
);
}

Related

Can't work out promises to return values - NodeJS

After searching through countless posts I don't understand why I'm still getting a Promise pending after my awaits. The code below should explain it but I'm trying to pull a MongoDB query of the max value of a column/schema. The console.log within the function is giving me the correct timestamp but I'm trying to pass that out of the inner scope and function to another function.
This is pure NodeJS with only MongoDB imported. Can this be done without any external packages?
export async function getMaxDate() {
var time = MongoClient.connect(url, { useUnifiedTopology: true }, function (err, db) {
if (err)
throw err;
var dbo = db.db(getDB);
dbo.collection(getColl)
.find()
.limit(1)
.sort({ 'timestamp': -1 })
.toArray(function (err, result) {
if (err)
throw err;
time = result[0].time; // THIS IS GIVING THE CORRECT VALUE
console.log(time)
db.close();
});
});
return time
}
export async function getMax() {
var block = await getMaxDate();
return block
}
var t = getMax();
console.log(t); // THIS IS GIVE ME A PROMISE PENDING
getMax() returns a promise, you have to wait for it.
var t = await getMax()
Also getMaxDate uses an async callback that you want to promisify:
export async function getMaxDate() {
return new Promise((resolve,reject) => {
MongoClient.connect(url, { useUnifiedTopology: true }, function (err, db) {
if (err)
return reject(err);
var dbo = db.db(getDB);
dbo.collection(getColl)
.find()
.limit(1)
.sort({ 'timestamp': -1 })
.toArray(function (err, result) {
if(err)
reject(err);
else {
let time = result[0].time; // THIS IS GIVING THE CORRECT VALUE
console.log(time)
db.close();
resolve(time);
}
});
})
});
}
For reference, A is the same thing as B here:
async function A(x) {
if(x)
throw new Error('foo');
else
return 'bar';
}
function B(x) {
return new Promise((resolve,reject)=>{
if(x)
reject(new Error('foo'));
else
resolve('bar');
});
}
Promises came first, then async/await notation was introduced to make common Promise coding practices easier
You can use A and B interchangeably:
async function example1() {
try {
await A(1);
await B(0);
}
catch(err) {
console.log('got error from A');
}
}
async function example2() {
return A(0).then(()=>B(1)).catch((err)=>{
console.log('got error from B');
})
}

How to make a function return both a promise and a callback

Today, when I was working with node, I met some special async functions with "overloads" that accept both promises and callbacks. Like this:
doSomething(result => {
console.log(result)
})
doSomething()
.then(result => console.log(result))
And probably this:
const result = await doSomething()
console.log(result)
I tried to implement this in my code but was unsuccessful. Any help would be appreciated.
You can make a function like this by creating a promise, then chaining on that promise with the argument if there is one, and then returning that chained promise. That will make it call the callback at the appropriate time, as well as giving you access to the promise that will complete when the callback completes. If you want the original promise even when there's a callback (not the chained version), then you can return that instead, by still chaining but then returning the original promise instead.
function f(cb) {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(123), 1000);
});
if (cb) {
return promise.then(cb);
} else {
return promise;
}
}
// usage 1
f(console.log)
// usage 2
f().then(console.log)
Let's say you had a function that was going to read your config file and parse it and you wanted to support both versions. You could do that like this with two separate implementation inside. Note, this has full error handling and uses the nodejs calling convention for the callback that passes parameters (err, result):
function getConfigData(filename, callback) {
if (typeof callback === "function") {
fs.readFile(filename, function(err, data) {
try {
if (err) throw err;
let result = JSON.parse(data);
callback(null, result);
} catch(e) {
callback(err);
}
});
} else {
return fs.promises.readFile(filename).then(data => {
return JSON.parse(data);
}).
}
}
This could then be used as either:
getConfigData('./config.json').then(result => {
console.log(result);
}).catch(err => {
console.log(err);
});
configData('./config.json', (err, data) => {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
Depending upon the specific asynchronous operation, it may be better or more efficient to have two separate implementations internally or to have one implementation that you adapt at the end to either a callback or a promise.
And, there's a useful helper function if you adapt a promise to a callback in multiple places like this:
function callbackHelper(p, callback) {
if (typeof callback === "function") {
// use nodejs calling convention for callbacks
p.then(result => {
callback(null, result);
}, err => {
callback(err);
});
} else {
return p;
}
}
That lets you work up a simpler shared implementation:
function getConfigData(filename, callback) {
let p = fs.promises.readFile(filename).then(data => {
return JSON.parse(data);
});
return callbackHelper(p, callback);
}

UnhandledPromiseRejectionWarning: Callback was already called (Loopback remote method)

I'm getting an error that says
(node:27301) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Callback was already called.
From what I understand about rejecting promises in await's and per the Mozilla description:
If the Promise is rejected, the await expression throws the rejected value.
I reject the error in the callback that's wrapped around my Promise like so:
Airport.nearbyAirports = async (location, cb) => {
let airports
try {
airports = await new Promise((resolve, reject) => {
Airport.find({
// code
}, (err, results) => {
if (err)
reject(err) // Reject here
else
resolve(results)
})
})
} catch (err) { // Catch here
cb(err, null)
return
}
if (!airports.empty)
cb(null, airports)
}
My question is
Why does it still consider my promise rejection unhandled? I thought the catch statement should silent this error.
Why does it consider my callback already called? I have a return statement in my catch, so both should never be called.
The problem was actually my framework (LoopbackJS), not my function. Apparently at the time of writing this, using promises are not supported:
https://loopback.io/doc/en/lb3/Using-promises.html#setup
Meaning I can't even use await in my function because the remote method wraps my function somewhere else, so async would always be unhandled. I ended up going back to a Promise-based implementation of the inner code:
Airport.nearbyAirports = (location, cb) => {
const settings = Airport.dataSource.settings
const db = DB(settings)
let airports
NAME_OF_QUERY().then((res) => {
cb(null, res)
}).catch((err) => {
cb(err, null)
})
If Airport.find() throws an exception, then execution will jump to your catch block and your Promise will never be resolved or rejected. Perhaps you need to wrap it in its own try/catch:
Airport.nearbyAirports = async (location, cb) => {
let airports
try {
airports = await new Promise((resolve, reject) => {
try {
Airport.find({
// code
}, (err, results) => {
if (err)
reject(err) // Reject here
else
resolve(results)
})
} catch (err) {
reject(err) // Reject here too
cb(err, null)
}
})
} catch (err) { // Catch here
cb(err, null)
return
}
if (!airports.empty)
cb(null, airports)
}
As said here, loopback 3 support this by allowing you to use a simple return.
This :
Entry.findFooById = async (id, cb) => {
const result = await Entry.findById(id);
return result;
};
...Is equivalent to :
Entry.findFooById = (id, cb) => {
Entry.findById(id)
.then(result => cb(null, result))
.catch(cb);
};
We use Loopback 2.31.0 and it also supports simple return for async functions used for remote methods. If you put a break-point somewhere in your remote method and jump one level above it in the call-stack you will see how it is implemented in loopback itself (shared-method.js):
// invoke
try {
var retval = method.apply(scope, formattedArgs);
if (retval && typeof retval.then === 'function') {
return retval.then(
function(args) {
if (returns.length === 1) args = [args];
var result = SharedMethod.toResult(returns, args);
debug('- %s - promise result %j', sharedMethod.name, result);
cb(null, result);
},
cb // error handler
);
}
return retval;
} catch (err) {
debug('error caught during the invocation of %s', this.name);
return cb(err);
}
};
What it does here - it calls your function and if it is an async function - it will return a promise (retval.then === 'function' will be true). In this case loopback will handle your result correctly, as a promise. It also do the error check for you, so you no longer try/catch blocks in your code anymore.
So, in your own code you just need to use it like below:
Airport.nearbyAirports = async (location) => {
let airports = await new Promise((resolve, reject) => {
Airport.find({
// code
}, (err, results) => {
if (err)
reject(err) // Reject here
else
resolve(results)
})
});
if (!airports.empty)
return airports;
}
else {
return {}; // not sure what you would like to return here as it wan not handled in your sample...
}
}
Note, you do not need to use callback (cb) at all here.

How do I return Cloudant insert status?

I have the following code:
updateDocument = (data) => {
Cloudant(CONN_STRING, (err, cloudant) => {
if (err) {
throw new Error('Failed to initialize Cloudant: ' + err.message);
}
let db = cloudant.db.use('my-db')
db.insert(data, (err, result) => {
if (err) {
throw new Error('Failed to initialize Cloudant: ' + err.message);
}
else {
console.log(result);
}
})
});
}
I would like for updateDocument() to return the result db.insert provides. However, if I try to return a variable it is undefined (I believe because of the async calls). I've tried async and await, but I may not have set them up properly because they also didn't work.
Thanks for any help!
This is a "JavaScript is asynchronous" problem. You could simplify your updateDocument function like so:
updateDocument = (data) => {
var cloudant = Cloudant({ url: CONN_STRING, plugin: 'promises');
let db = cloudant.db.use('my-db')
return db.insert(data);
}
This is using the Cloudant library with the "promises" plugin. JavaScript Promises help you manage asynchronous calls without passing callback functions around.
You would call this code like so:
updateDocument({a: 1, :b})
.then((data) => {
console.log('success!', data);
}.catch((e) => {
console.log('oops!', e);
});
Success arrives at 'then', failure arrives in the 'catch' clause.

NodeJS Promises - "Then" not called

I'm new to NodeJS and after spending a few hours trying to understand how Promises work exactly, what seems to be an easy thing still doesn't work.
I'm trying to make a few calls to a database, and once all of those calls are done, do something else. What I have now is the following code, but none of the then-functions are called.
var queries = ['SELECT value FROM tag', 'SELECT url FROM download_link'];
console.log("Starting promises");
var allPromise = Promise.all([queryDatabaseAddToResult(connection, queries[0], result), queryDatabaseAddToResult(connection, queries[1], result)]);
allPromise.then(
function(result) {
console.log("1"); // Does not show up
}, function(err) {
console.log("2"); // Does not show up either
}
);
function queryDatabaseAddToResult(connection, query, result) {
return new Promise(function(resolve, reject) {
connection.query(query, function(err, rows, fields) {
if (err) {
console.log(err);
Promise.reject(err);
}
console.log(rows);
result.tags = JSON.stringify(rows);
Promise.resolve(result);
});
})
}
The calls to the database do get made, as logging the rows show up in the log.
The problem is that you are not calling the correct resolve and reject functions. It should be:
function queryDatabaseAddToResult(connection, query, result) {
return new Promise(function(resolve, reject) {
connection.query(query, function(err, rows, fields) {
if (err) {
console.log(err);
reject(err);
} else {
console.log(rows);
result.tags = JSON.stringify(rows);
resolve(result);
}
});
})
Note that the resolve and reject calls should not be scoped with Promise.. And you should have used an else to avoid calling resolve once you've called reject.
you have to do like this:
var promise1 = queryDatabaseAddToResult(connection, queries[0], result);
var promise2 = queryDatabaseAddToResult(connection, queries[1],result);
Promise.all([prromise1, promise2]).then(result => {
console.log(1);
}).catch(err => {
console.log(err);
});
return Promise.reject() //no need to add else part

Resources