I’m working through some inherited code using promises and having a major issue. I have two functions, both returning a promise (or a series of promises in the findOne case), where findAll makes calls to findOne to loop through a number of records to query the data from those records in other tables using Sequelize. I've tried to lay out the code so that all promises passed to findOne are resolved or rejected before returning to the parent function, findAll.
Given a number of matching records in findAll, I pass each record to findOne, which by the time findOne resolves it calls another function, constructJson, which returns a value and not a promise. The issue I'm seeing is that once all records pass through findOne and are returned to findAll, the code actually goes back to the resolve(constructJson(_map, scrub)) line the findOne function a number of times. In my test use case where I’m supposed to be returned four distinct record IDs (that are passed into findOne to be queried), findOne is returning duplicated or out-of-order IDs. I very rarely get the same four sequential IDs returned in the same order they were passed in to findOne, though based on the flow of the function it seems like this should be the case. There is a piece to the asynchronicity I am missing in order to ensure the promise returned by findOne is fully completed the the associated IDs (queries object) passed into it are returned in order. What am I not seeing?
const Sequelize = require('sequelize');
function findAll(dbConn, _map, queries, joins, primaryTable, primaryKey, whereClause, limit, offset, scrub) {
return new Promise((resolve, reject) => {
const sequelize = new Sequelize(dbConn.db, dbConn.username, dbConn.password, dbConn.options);
offset = isNaN(offset) ? 0 : offset;
whereClause = !!whereClause ? ` WHERE true${whereClause}` : ' WHERE true';
sequelize.query(`SELECT count(${primaryKey}) FROM ${primaryTable}${whereClause}`, {type: sequelize.QueryTypes.SELECT})
.then(result => {
totalRows = result[0]["count"];
});
whereClause = isNaN(limit) ? whereClause : whereClause + ` LIMIT ${limit} OFFSET ${offset}`;
sequelize.query(`SELECT ${primaryKey} FROM ${primaryTable}${whereClause}`, {type: sequelize.QueryTypes.SELECT})
.then(matchingRecords => {
let promiseStack = [];
matchingRecords.forEach(record => {
const cloneOfMap = JSON.parse(JSON.stringify(_map));
promiseStack.push(
findOne(dbConn, cloneOfMap, queries, joins, primaryTable, primaryKey, record.id, scrub)
.then(result => {
return result;
})
.catch(error => {
return {err: `error collecting data for id = ${record.id}\n${error}`};
})
)
});
Promise.all(promiseStack).then(bundleEntries => {
sequelize.close();
let errors = bundleEntries.filter(r => {return !!r.err}).map(r => r.err);
if (errors.length === 0) {
resolve(bundleEntries);
} else {
reject("The following queries failed:\n" + errors.join('\n'));
}
});
})
.catch(error => {
sequelize.close();
reject(error);
});
});
}
function findOne(dbConn, _map, queries, joins, primaryTable, primaryKey, id, scrub) {
return new Promise((resolve, reject) => {
let promiseStack = [];
let noMatchesReturned = true;
const sequelize = new Sequelize(dbConn.db, dbConn.username, dbConn.password, dbConn.options);
let queryString;
queries.forEach(query => {
if (!!query.sql && query.sql !== '') {
switch (dbConn.options.dialect) {
case "postgres":
queryString = query.sql + ` WHERE ${primaryTable}.${primaryKey}='${id}' LIMIT 1`;
break;
case "mssql":
queryString = query.sql + ` WHERE ${primaryTable}.${primaryKey}='${id}'`;
break;
}
}
else {
let otherTable = '';
let joinString = '';
if (query.table !== primaryTable) {
otherTable = ", " + primaryTable;
joins.filter(j => {return j.fkTable === primaryTable && j.pkTable === query.table}).forEach(j => {
joinString += ` AND ${j.fkTable}.${j.fkColumn} = ${j.pkTable}.${j.pkColumn}`;
});
}
switch (dbConn.options.dialect) {
case "postgres":
queryString = `SELECT ${query.table}.${query.column} FROM ${query.table}${otherTable} WHERE ${primaryTable}.${primaryKey}='${id}'${joinString} LIMIT 1`;
break;
case "mssql":
queryString = `SELECT TOP 1 ${query.table}.${query.column} FROM ${query.table}${otherTable} WHERE ${primaryTable}.${primaryKey}='${id}'${joinString}`;
break;
}
}
// console.log(queryString);
promiseStack.push(
sequelize.query(queryString, {type: sequelize.QueryTypes.SELECT})
.then(row => {
let newData;
if (row.length === 0) {
newData = null;
} else {
noMatchesReturned = false;
const x = row[0];
const y = Object.keys(x)[0];
newData = x[y];
}
return Object.assign(query, {data: newData});
})
.catch(error => {
return {err: `Error executing query ${error.sql}\n\t${error}`};
})
);
});
Promise.all(promiseStack).then(rows => {
// identify queries that returned an error
sequelize.close();
let errors = rows.filter(r => {return !!r.err}).map(r => r.err);
if (errors.length === 0) {
if (noMatchesReturned) {
resolve([]);
}
else {
// all queries completed without error
rows.forEach(result => {
_map.rows[result.id].data = result.data;
});
resolve(constructJson(_map, scrub));
}
}
else {
sequelize.close();
reject("The following queries failed:\n" + errors.join('\n'));
}
}, error => {
sequelize.close();
reject(error);
});
});
}
Related
When calling this function (right now my order table is empty) I exspect the number 1
let id = 0;
id = await orderNumber();
But the id I get in return is "[object Object]1"
//
// Get MAX order number
//
orderNumber = () => {
return new Promise((resolve, reject) => {
return resolve (orderModel.find().count()+1)
})
}
Anyone have an idea how to avoid that?
My purpose here is just to find the MAX number of records in orderModel (order table) and add 1 to that number.
orderModel.find().count() probably returns a Promise, so you need to await for it to get the actual count:
Note: as mentioned by torbenrudgaard, you're better off using .countDocuments()
orderNumber = async () => {
const count = await orderModel.find().count()
return count + 1
}
or:
orderNumber = () => {
return orderModel.find().count().then(count => count + 1)
}
id = await orderNumber();
orderNumber = () => {
return new Promise((resolve, reject) => {
orderModel.count({}, function(err, result) {
if (err) {
console.log(err);
} else {
res.json("Number of documents in the collection: " + result);
return resolve({
count: result
});
}
});
}
In the below code I am trying to upsert to user records. If either record fails to upsert, I would like both records to be rolled back.
In the below code I force the second record to fail by setting user_id = null. However, it still hits the then block return result;. It does not catch/throw an error or rollback the transaction. I also see this error in my logs:
Unhandled rejection SequelizeValidationError: notNull Violation: User.user_id cannot be null
at Promise.all.then
async function submitUsers(users) {
return db.sequelize.transaction(async (tx) => {
const queries = users.map((user, index) => {
if (index == 1) {
user.user_id = null;
}
User.upsert(user, tx);
});
await Promise.all(queries);
}).then((result) => {
return result;
}).catch((e) => {
throw e;
});
}
const users = [ {user_id: 1}, {user_id: 2}];
await submitUsers(users);
you are not returning your promises correctly. try this;
async function submitUsers(users) {
return db.sequelize.transaction((tx) => {
const queries = users.map((user, index) => {
if (index == 1) {
user.user_id = null;
}
return User.upsert(user, tx);
});
return Promise.all(queries);
}).then((result) => {
return result;
}).catch((e) => {
throw e;
});
}
I am trying to finish a login functionality with mysql and express. I got a work_id and a user_password, and I want to use the work_id to find whether the user exists in my database. I use promise to do this, I can log the selected user information in the console, but the promise is always pending, and the web storm console didn't terminate.
What I want is a boolean value from the promise, whether the user exists or not.
Here is my code:
query.js.
const pool = require('./connect');
module.exports = {
query: function (sqlString, params) {
return new Promise((resolve, reject) => {
pool.getConnection(function (err, connection) {
if (err) {
reject(err)
} else {
connection.query(sqlString, params, (err, rows) => {
if (err) {
reject(err)
} else {
resolve(rows)
}
connection.release()
})
}
})
})
}
}
sqlCRUD.js, about the sql statement
const user = {
queryByWorkId: 'select * from user_info where work_id=?',
queryAll: 'select * from user_info',
resetPassword: 'update user_info set user_password = ? where work_id = ?',
};
user.js, I execute the test here.
const Model = require('./main')
const crypto = require('crypto')
const _ = require('./query')
const $sqlQuery = require('./sqlCRUD').user
class User{
// others
static findOne(form={}) {
const { work_id, user_password } = form
return _.query($sqlQuery.queryByWorkId, work_id)
.then(res => {
console.log(res)
if (res.length > 0) {
const u = res[0]
return u
}
return false
})
.catch(err => {
console.log('User.findOne error', err)
return {
errmsg: JSON.stringify(err)
}
})
}
Here is my test, in user.js
const test = () => {
const form = {
work_id: '007',
user_password: 'root',
}
const r = User.findOne(form)
console.log('r', r)
}
And this is the output:
I am not allowed to embed a picture here, so SO generates a link
I got confused about this: in my query.js file, I return a promise, in my User.findOne(form={}) method, I call it with a then and catch,
return _.query($sqlQuery.queryByWorkId, work_id).then(res => console.log(res)).catch(err => console.log(err)), but the console did't terminate, and I just got a Promise { }.
What's wrong with my code? How can I get a value returned from a then clause in promise when select data using mysql? Thanks in advance.
I'm trying to write a chain of Promises but the last .then() is being called multiple times and I don't know why. The last .then() must run a single time because it will call another API passing result as body.
I know that is being called multiple times because I'm logging as console.log().
What is wrong on my code? For my understand, then() should wait promise returns something.
app.post('/router/join', function(req, res){
let data = req.body;
sessions.validate(data)
.then(result => {
return {
authenticated: (result.code === 201)
};
})
.then(result => {
if(result.authenticated){
return contacts.getContacts(data.tenant_id).then(cs => {
let json = merge(result, cs.data);
return Promise.all(cs.data.items.map(contact => {
return messages.getLastMessage(data.tenant_id, contact.item.contact_id, data.hash_id)
.then(result => {
contact.item.last_message = result.code === 200 && result.data.length > 0 ? result.data[0] : null;
return contact;
});
})).then(result => {
json.items = result;
return json;
});
});
} else {
return result;
}
})
.then(result => {
//this call should run after all other promises and only a single time
let event = result.authenticated ? 'valid_session' : 'invalid_session';
console.log('222');
proxy.send(event, result)}
)
.catch(err => {
console.log('333');
proxy.send('invalid_session', {socket_id: data.socket_id})
})
res.status(201).send({});
});
You can use async/await to clean it up. Inside async functions you can await the results of promises.
app.post('/router/join', async function (req, res, next) {
try {
let data = req.body;
let {code} = await sessions.validate(data);
let result = { authenticated: (code === 201) };
if (result.authenticated) {
let cs = await contacts.getContacts(data.tenant_id);
let json = merge(result, cs.data);
let items = Promise.all(cs.data.items.map(async contact => {
let result = await messages.getLastMessage(data.tenant_id, contact.item.contact_id, data.hash_id)
contact.item.last_message = result.code === 200 && result.data.length > 0 ? result.data[0] : null;
return contact;
}));
json.items = items;
result = json;
}
let event = result.authenticated ? 'valid_session' : 'invalid_session';
console.log('222');
proxy.send(event, result);
res.status(201).send({});
} catch (err) {
proxy.send('invalid_session', {socket_id: data.socket_id})
next (err);
}
});
One of the biggest issue we face now with parse-server is duplication. Although we have implemented a Parse cloud code to prevent such event through beforeSave and afterSave methods at the same time added external middleware to check for existing object before saving still we face duplication over and over specially on concurrent operations.
Here is our code to prevent duplication for a specific class:
Parse.Cloud.beforeSave("Category", function(request, response) {
var newCategory = request.object;
var name = newCategory.get("name");
var query = new Parse.Query("Category");
query.equalTo("name", name);
query.first({
success: function(results) {
if(results) {
if (!request.object.isNew()) { // allow updates
response.success();
} else {
response.error({errorCode:400,errorMsg:"Category already exist"});
}
} else {
response.success();
}
},
error: function(error) {
response.success();
}
});
});
Parse.Cloud.afterSave("Category", function(request) {
var query = new Parse.Query("Category");
query.equalTo("name", request.object.get("name"));
query.ascending("createdAt");
query.find({
success:function(results) {
if (results && results.length > 1) {
for(var i = (results.length - 1); i > 0 ; i--) {
results[i].destroy();
}
}
else {
// No duplicates
}
},
error:function(error) {
}
});
});
This code above is able to prevent some duplicate but most still goes through, example:
What is the "ultimate way" to prevent duplication with Parse server?
You can always create a unique index in mongodb for the field that should be unique in your document.
This way any save that conflicts with that index, will be aborted
Maybe you should write something with Promises like :
Parse.Cloud.beforeSave("Category", function (request, response) {
return new Promise((resolve, reject) => {
var query = new Parse.Query("Category");
query.equalTo("name", "Dummy");
return query.first().then(function (results) {
resolve(); // or reject()
});
})
});
Parse.Cloud.beforeSave("Category", async (request) => {
(...)
await results = query.first();
// then your logic here
response.success();
response.error({ errorCode: 400, errorMsg: "Category already exist" })
})
Here is my Solution:
Parse.Cloud.beforeSave( 'ClassName', async ( request ) => {
const columnName = 'columnName'
const className = 'ClassName'
if( request.object.isNew() ) {
var newCategory = request.object
var name = newCategory.get( columnName )
var query = new Parse.Query( className )
query.equalTo( columnName, name )
const results = await query.count()
if( results === 0 ) {
// no response.success needed
// https://github.com/parse-community/parse-server/blob/alpha/3.0.0.md
} else {
throw 'Is not unique';
}
}
} )