callback failed to run after sqlite statment.all query - javascript

In the following function, I have attempted to run an statment.all query in sqlite3.
however the function ends without ever running the callback function.
I cannot pinpoint the problem even after a thorough debug and few experiments.
The strangest thing about this is that I have another similar query working perfectly fine within the code. It's included as well in the question.
here's the problematic code:
function gameSetup (questions_num){
var questions = [];
getQuestions.all(questions_num, function (e, rows) {
console.log("the callback is working!"); // this line never runs
if (e) {
return new Promise( (resolve, reject) => {
reject(e);
});
}
else{
var answers, question;
for (let row of rows)
{
answers = [row.correct_ans, row.ans2, row.ans3, row.ans4];
answers = shuffle(answers);
question = {"question":row.question, "ans1":answers[0] , "ans2":answers[1] , "ans3":answers[2] , "ans4":answers[3]};
questions.push(question);
console.log(questions);
}
}
return new Promise( (resolve, reject) => {
resolve(questions);
});
});}
this is the sql statement:var getQuestions = db.prepare("select * from t_questions order by random() limit ?");
and I have another similar code that works :
app.get("/rooms", checkAuth, (req, res) => {
getRooms.all((e, data) => {
if(e) {
return res.status(500).json(e);
}
else if (data.length == 0) {
res.render('rooms', {items: data, error: "no rooms found"});
}
else
{
res.render('rooms', {items: data, error: "false"});
}
});
});
with the following statement:
var getRooms = db.prepare("select * from t_games where start_time is null");
I might have given too much info, or too little. feedback on the question would be appreciated since this is my first question on the site.

I think calling random() in the statement might be where the problem is.
What happens if you try something like:
var getQuestions = db.prepare("select * from t_questions order by ? limit ?")
and
getQuestions.all([random(), questions_num], function (e, rows) { ...
Also, is random() a function you defined somewhere that returns the column to be ordered by? Or maybe Math.random()?

Related

why am I getting undefined on this function?

I'm building a dialogflow agent that uses Airtable as database (library: airtable js)
Everything works fine except I can't get the value "out of" the function in order to send it back to the dialogflow agent.
Function
function showSinglePrice(agent) {
var finalPrice;
var arraySinglePrice = null;
const item = agent.context.get("item"),
place = item.parameters.place,
size = item.parameters.size,
type = item.parameters.type;
base(tablePlaces)
.select({
maxRecords: 10,
view: viewName,
filterByFormula: `AND({type} = "${type}",{size} = "${size}",{place} = "${place}")`
})
.firstPage(function(error, records) {
if (error) {
response.send({ error: error });
} else {
arraySinglePrice = records.map(record => {
return {
price: record.get("price")
};
});
console.log(arraySinglePrice); //this works fine
finalPrice = arraySinglePrice[0].price; //this works fine
return finalPrice;
}
});
agent.add(`I wanted to get the result in here: ${finalPrice}`); //undefined
}
I'm new to asynchronous programming, so I'm probably messing up with the Airtable js promises, but can't figure it out how to get it to work.
Would appreciate any help
EDIT
THANKS #PRISONER FOR THE HELP.
FOR THOSE IN NEED, HERE IS THE WORKING CODE:
function showSinglePrice(agent) {
const item = agent.context.get("item"),
place = item.parameters.place,
size = item.parameters.size,
type = item.parameters.type;
return base(tablePlaces) //defined variable before this function
.select({
maxRecords: 1, //just want 1
view: viewName, //defined variable before this function
filterByFormula: `AND({type} = "${type}",{size} = "${size}",{place} = "${place}")`
})
.firstPage()
.then(result => {
console.log(result);
var getPrice = result[0].fields.price;
agent.add(`the current price is: $ ${getPrice}`); //its working
})
.catch(error => {
console.log(error);
response.json({
fulfillmentMessages: [
{
text: {
text: ["We got the following error..."] //will work on it
}
}
]
});
});
}
You're correct, there are some issues with how you're using Promises. You're using a callback function in your call to firstPage() instead of having it return a Promise. So you could have written that part to look something like this:
.firstPage()
.then( records => {
// Work with the records here
})
.catch( err => {
// Deal with the error
});
Once you're dealing with Promises, everything you want to do must be done inside the .then() block. So you'll need to move the agent.add() in there.
You also need to return the Promise, so Dialogflow knows that theres an asynchronous operation taking place. Since the .then() and .catch() functions return a Promise, you can just return the result of the whole expression. So something like
return base(tablePlaces)
.select(query)
.firstPage()
.then(/*function*/)
.catch(/*function*/);

Creating my own sql wrapper for nodejs/express

I'm trying to create my own wrapper for mysql for my nodejs application. I have two questions here one of which theres a work around and one where I'm unsure what to do as my javascript skills are still in the learning phase.
First thing: As of right now when you navigate to /api/finance it directs you to the finance controller and the index method. This is currently just for testing purposes trying to figure out how to this kind of stuff.
FinanceController:
const sql = require('../../sql.js')
module.exports = {
index: (req, res, next) => {
sql.get('test').then((result) => {
res.status(200).json(result);
})
}
}
sql.js
const mysql = require('mysql');
const { DB } = require('./config')
var connection = mysql.createConnection(DB)
module.exports = {
get: function(table, columns = '*') {
return new Promise((resolve, reject) => {
connection.query('SELECT ?? FROM ?? ', [columns, table], function (error, results, fields) {
if (error) throw error;
resolve(results);
});
})
},
all: function(table) {
return new Promise((resolve, reject) => {
connection.query('SELECT * FROM ?? ', table, function (error, results, fields) {
if (error) throw error;
resolve(results);
});
})
},
where: function(){
console.log('where called')
}
}
As you can see, I have a get() and all(). get() allows you to pass the table name and an array of columns for example: ['id', 'name'] would get you the id column and name column. columns = '*' was an attempt on being able to use one function to either get all columns of the table or specify specific columns however it returns an error: Unknown column in 'field list' so all() was my "workaround" however i'd like it to be one function.
Next I can't figure out how to stack/pipe methods? if thats the word.
The goal here would be so I could call the function like this:
index: (req, res, next) => {
sql.all('test').where().then((result) => {
res.status(200).json(result);
})
}
}
obviously within the .where() I would have it like: .where('id', '=', 'userID') or something along those lines.
however I'm unsure on how to go about doing that and would like some guidance if its possible. I receive the error: sql.all(...).where is not a function
Instead of immediately launching the SQL, you should simply register the provided information in an instance object (having the same methods) and return that object, and let each method enrich the SQL until the end of the chain is reached and you call a method that will launch the SQL.
The object that is passed from one method to the next (as this) maintains state, and collects the different elements of the SQL statement.
Here is one way to do it.
NB: In this demo I used a mock-object for connection. This mock object will not actually connect to a database. Its query method will just produce the final SQL (with all ? resolved) instead of a real data set.
// Mock connection object for this snippet only. Just produces the final SQL, not the result
var connection = {
query(sql, params, callback) {
let i = 0;
sql = sql.replace(/\?\??/g, (m) => {
if (m.length > 1) return [].concat(params[i++]).map(p => "`" + p + "`").join(", ");
if (typeof params[i] === "string") return "'" + params[i++].replace(/'/g, "''") + "'";
return params[i++];
});
setTimeout(callback(null, sql));
}
}
// Function to create an instance when a first method call is made on the `sql` object directly
// Chained method calls will keep using that instance
function getInstance(inst) {
if (inst.columns) return inst; // Keep using same instance in the chain
inst = Object.create(inst); // No instance yet: create one
inst.table = null;
inst.params = [];
inst.columns = [];
inst.conditions = [];
inst.order = [];
return inst;
}
// This sql object serves a starting point as well
// as proto object for the instance object that
// passes through the chain:
var sql = {
from(table) {
let inst = getInstance(this);
inst.table = table;
return inst;
},
select(...columns) {
let inst = getInstance(this);
inst.columns = inst.columns.concat(columns);
return inst;
},
where(column, cmp, value) {
if (!["<",">","<=",">=","=","!="].includes(cmp)) throw "invalid operator";
let inst = getInstance(this);
inst.params.push(column, value);
inst.conditions.push(cmp);
return inst;
},
orderBy(...columns) {
let inst = getInstance(this);
inst.order = inst.order.concat(columns);
return inst;
},
promise() {
if (!this.table) throw "no table specified";
// build SQL and parameter list
let sql = "SELECT *";
let params = [];
if (this.columns.length && this.columns != "*") {
sql = "SELECT ??";
params.push(this.columns);
}
sql += " FROM ??";
params.push(this.table);
if (this.conditions.length) {
sql += " WHERE " + this.conditions.map(cmp => `?? ${cmp} ?`).join(" AND ");
params.push(...this.params);
}
if (this.order.length) {
sql += " ORDER BY ??";
params.push(this.order);
}
return new Promise(resolve => {
connection.query(sql, params, function (error, results) {
if (error) throw error;
resolve(results);
});
});
}
};
// demo
sql.from("customer")
.select("id", "name")
.where("name", ">", "john")
.where("name", "<", "thomas")
.orderBy("name", "id")
.promise()
.then(console.log);
Note that in this implementation it does not matter in which order you chain the from, select, where and order method calls. You could even do the following if you wanted to:
sql .orderBy("name", "id")
.where("name", ">", "john")
.from("customer")
.where("name", "<", "thomas")
.select("id", "name")
.promise()
.then(console.log);

nodejs mysql doesn't executes query and stops the script

I have a database class with some methods to easily do some queries without formatting the values into a sql query string. So here is my delete method where the script is stopping.
// Other Module
let filter = {
url: 'http://www.example.com/some/route',
},
DB = new MySQL();
DB.delete(filter, 'found_urls').then(result => {
console.log('The row/s are deleted.');
}).catch(error => {
console.log('An error occured.');
console.log(error);
});
// MySQL.js
class MySQL {
// some methods....
delete(filter, table, operator = '=') {
// Parse where clauses and create query string.
let whereCondition = MySQL.parseColumnValue(filter, operator),
sql = `DELETE FROM ${table}`;
// whereCondition is an array: ["url = 'http://www.example.com/some/route'"]
if (whereCondition.length > 1) {
sql += ` WHERE ${whereCondition.join(' AND ')}`;
}
console.log(sql);
// I get "DELETE FROM found_urls WHERE url = 'http://www.example.com/some/route'"
return new Promise((resolve, reject) => {
console.log('You can see me.');
this.database.query(sql, (error, result) => {
console.log('You never see me because the script stopped.');
if (error) {
reject(error);
return;
}
resolve(result);
});
});
}
}
Some days ago it worked perfect but now it stops. I don't know what's wrong with this code.
UPDATE
This class would be used in a webcrawler so I think it's not necessary to protect it for SQL injection.
The table found_urls has about 200 rows, nothing is locked.
Bug I found my problem: A failure in another module. Without this module the crawler is running (with the delete operations).

tmdb returns nothing and sometimes returns value

I'm trying to get a random movie title from tmdb, the code is working but there's a frustrating problem, some random numbers return absolutely nothing, how can I loop or do something or try random numbers till i get a result? I guess there are gaps in tmdb movies id numbers!
const tmdb = require('tmdbv3').init('---');
function randomMovie(callback){
var r = Math.floor(Math.random()*1000);
tmdb.movie.info(r, (err ,res) => {
var x = res.title
callback(x);
})};
randomMovie(function(title){console.log(title)})
If you inspect the response headers and look at the status code I bet you're getting a 404 (not found) back since the ID has been deleted. And you won't be getting "nothing back" the TMDB API returns a set of errors with codes for you to troubleshoot what is happening. That is documented here.
Depending on what you're doing, knowing what IDs are available in advance will help you. There are some downloadable files for this that you can read about in the documentation.
I would not recommend to use this API, because even his test are failing. The problem you face is that if theres a number without data, it doesn't even return its callback, so you cannot handle its error (because it doesn't throw any). This is the best i could get from that API:
'use strict';
const tmdb = require('tmdbv3').init('8b39b6f141f42e463b507151122d0971');
function randomMovie(callback) {
const r = Math.floor(Math.random() * 1000);
tmdb.movie.info(r, (err, res) => {
const x = res.title
callback(x);
})
};
function tryTenTimes() {
let firstTitle;
for (let i = 0; i < 10; i++) {
randomMovie(title => {
if (!firstTitle) {
firstTitle = title;
console.log(firstTitle);
}
});
}
}
tryTenTimes();
It's an ugly workaround, that does 10 ask, and takes the first valid one.
EDIT: I was asked to do one with a while loop, and this is how I managed to do it
'use strict';
const tmdb = require('tmdbv3').init('8b39b6f141f42e463b507151122d0971');
function randomMovie(callback) {
const r = Math.floor(Math.random() * 1000);
tmdb.movie.info(r, (err, res) => {
const x = res.title
callback(x);
})
};
function tryWhileTimes() {
let firstTitle;
while (!firstTitle) {
return new Promise((resolve, reject) => {
randomMovie(title => {
if (!firstTitle) {
firstTitle = title;
console.log(firstTitle);
resolve();
}
});
});
}
}
tryWhileTimes()

Unreliable behaviour in Node.js

I have a Node.js application that, upon initialisation, reads two tables from an SQL database and reconstructs their relationship in memory. They're used for synchronously looking up data that changes (very) infrequently.
Problem: Sometimes I can't access the data, even though the application reports successfully loading it.
Code:
constants.js
module.exports = {
ready: function () { return false; }
};
var log = sysLog('core', 'constants')
, Geo = require('../models/geo.js');
var _ready = false
, _countries = []
, _carriers = [];
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
var obj = country.toPlainObject()
, id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
_countries = countries;
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
_carriers = carriers;
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js
var log = sysLog('core', 'utils')
, constants = require('./constants');
module.exports = {
getCountryByISO: function(iso) {
if (!iso) {
return;
}
if ('string' != typeof iso) {
throw new Error('getCountryByISO requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
switch (iso.length) {
case 2:
return _.findWhere(constants.countries(), { 'iso2' : iso.toUpperCase() });
case 3:
return _.findWhere(constants.countries(), { 'iso3' : iso.toUpperCase() });
default:
throw new Error('getCountryByISO requires a 2 or 3 letter ISO code');
}
},
getCarrierByCode: function(code) {
if (!code) {
return;
}
if ('string' != typeof code) {
throw new Error('getCarrierByCode requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'code' : code });
},
getCarrierByHandle: function(handle) {
if (!handle) {
return;
}
if ('string' != typeof handle) {
throw new Error('getCarrierByHandle requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'handle' : handle });
}
};
Use case
if (data.handle) {
carrier = utils.getCarrierByHandle(data.handle);
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}
What's going on: All errors are logged; as soon as I see an error (i.e. "Unknown carrier") in the logs, I check the SQL database to see if it should've been recognised. That has always been the case so far, so I check the debug log to see if data was loaded. I always see "Loaded X countries" and "Loaded Y carriers" with correct values and no sign of "Could not load constants" or any other kind of trouble.
This happens around 10% of the time I start the application and the problem persists (i.e. didn't seem to go away after 12+ hours) and seems to occur regardless of input, leading me to think that the data isn't referenced correctly.
Questions:
Is there something wrong in constants.js or am I doing something very obviously wrong? I've tried setting it up for cyclical loading (even though I am not aware of that happening in this case).
Why can't I (sometimes) access my data?
What can I do to figure out what's wrong?
Is there any way I can work around this? Is there anything else I could to achieve the desired behaviour? Hard-coding the data in constants.js is excluded.
Additional information:
constants.reload() is never actually called from outside of constants.js.
constants.js is required only in utils.js.
utils.js is required in app.js (application entry); all files required before it do not require it.
SQL access is done through an in-house library built on top of knex.js and bluebird; so far it's been very stable.
Versions:
Node.js v0.10.33
underscore 1.7.0
bluebird 2.3.11
knex 0.6.22
}).finally(function () {
_ready = true;
});
Code in a finally will always get called, regardless of if an error was thrown up the promise chain. Additionally, your reload().catch(/* ... */) clause will never be reached, because finally swallows the error.
Geo.Country.find() or Geo.Carrier.Descriptor.find() could throw an error, and _ready would still be set to true, and the problem of your countries and carriers not being set would persist.
This problem would not have occurred if you had designed your system without a ready call, as I described in my previous post. Hopefully this informs you that the issue here is really beyond finally swallowing a catch. The real issue is relying on side-effects; the modification of free variables results in brittle systems, especially when asynchrony is involved. I highly recommend against it.
Try this
var log = sysLog('core', 'constants');
var Geo = require('../models/geo.js');
var index;
var _countries;
var _carriers;
function reload() {
index = Object.create(null);
_countries = Geo.Country.find().map(function (country) {
var obj = country.toPlainObject();
var id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
});
_carriers = _countries.then(function(countries) {
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
});
});
return _carriers;
}
reload().done();
module.exports = {
reload : reload,
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
constants.reload() is never actually called from outside of
constants.js.
That's your issue. constants.reload() reads from a database, which is an aysnchronous process. Node's require() is a synchronous process. At the time constants.js is required in utils.js and the module.exports value is returned, your database query is still running. And at whatever point in time that app.js reaches the point where it calls a method from the utils module, that query could still be running, resulting in the error.
You could say that requiring utils.js has the side-effect of requiring constants.js, which has the side-effect of executing a database query, which has the side-effect of concurrently modifying the free variables _countries and _carriers.
Initialize _countries and _carriers as unresolved promises. Have reload() resolve them. Make the utils.js api async.
promises.js:
// ...
var Promise = require('bluebird');
var countriesResolve
, carriersResolve;
var _ready = false
, _countries = new Promise(function (resolve) {
countriesResolve = resolve;
})
, _carriers = new Promise(function (resolve) {
carriersResolve = resolve;
});
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
// ...
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
countriesResolve(countries);
return Geo.Carrier.Descriptor.find().map(function (carrier) {
// ...
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
carriersResolve(carriers);
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js
getCarrierByHandle: function(handle) {
// ...
return constants.carriers().then(function (carriers) {
return _.findWhere(carriers, { 'handle' : handle });
});
}
Use case:
utils.getCarrierByHandle(data.handle).then(function (carrier) {
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}).then(function () {
// ... next step in application logic
});
This design will also eliminate the need for a ready method.
Alternatively, you could call constants.reload() on initialization and hang all possibly-dependent operations until it completes. This approach would also obsolete the ready method.
What can I do to figure out what's wrong?
You could have analyzed your logs and observed that "Loaded X countries" and "Loaded Y carriers" were sometimes written after "Unknown carrier", helping you realize that the success of utils.getCarrierByHandle() was a race condition.

Categories