Snippets are from a node.js and mongoDB CRUD application.Github repo for full code. The code is working fine but unsure if my structure and use of promises and async await are bad practice.
handlers._newbies = {};
handlers._newbies.post = (parsedReq, res) => {
const newbie = JSON.parse(parsedReq.payload);
databaseCalls.create(newbie)
.then((result) => {
res.writeHead(200,{'Content-Type' : 'application/json'});
const resultToString = JSON.stringify(result.ops[0]);
res.write(resultToString);
res.end();
})
.catch(err => console.log(err));
};
const databaseCalls = {};
databaseCalls.create = (newbie) => {
return new Promise(async (resolve, reject) => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
console.log("Connected correctly to server");
const db = client.db('Noob-List');
const result = await db.collection('newbies').insertOne(newbie);
client.close();
resolve(result);
} catch(err) {
console.log(err);
}
});
};
When the node server gets a POST request with the JSON payload, it calls the handlers._newbies.post handler which takes the payload and passed it to the
const newbie = JSON.parse(parsedReq.payload);
databaseCalls.create(newbie)
call. I want this database call to return a promise that holds the result of the db.collection('newbies').insertOne(newbie);
call. I was having trouble doing this with just returning the promise returned by the insertOne because after returning I cant call client.close();.
Again maybe what I have done here is fine but I haven't found anything online about creating promises with promises in them. Thank you for your time let me know what is unclear with my question.
It is considered an anti-pattern to be wrapping an existing promise in a manually created promise because there's just no reason to do so and it creates many an opportunities for error, particular in error handling.
And, in your case, you have several error handling issues.
If you get an error anywhere in your database code, you never resolve or reject the promise you are creating. This is a classic problem with the anti-pattern.
If you get an error after opening the DB, you don't close the DB
You don't communicate back an error to the caller.
Here's how you can do your .create() function without the anti-pattern and without the above problems:
databaseCalls.create = async function(newbie) {
let client;
try {
client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
console.log("Connected correctly to server");
const db = client.db('Noob-List');
return db.collection('newbies').insertOne(newbie);
} catch(err) {
// log error, but still reject the promise
console.log(err);
throw err;
} finally {
// clean up any open database
if (client) {
client.close();
}
}
}
Then, you would use this like:
databaseCalls.create(something).then(result => {
console.log("succeeded");'
}).catch(err => {
console.log(err);
});
FYI, I also modified some other things:
The database connection is closed, even in error conditions
The function returns a promise which is resolved with the result of .insertOne() (if there is a meaningful result there)
If there's an error, the returned promise is rejected with that error
Not particularly relevant to your issue with promises, but you will generally not want to open and close the DB connection on every operation. You can either use one lasting connection or create a pool of connections where you can fetch one from the pool and then put it back in the pool when done (most DBs have that type of feature for server-side work).
Related
I'm using crypto.generateKeyPair inside a post endpoint in Express.
I need to insert the key generated inside my DB and then return within the endpoint the id row of the inserted row.
The endpoint code reads as:
app.post('/createKeys', (req, res) => {
crypto.generateKeyPair('rsa',,
(err, publicKey, privateKey) => {
if(!err) {
let id = myfunction(publicKey.toString('hex'),
privateKey.toString('hex'));
console.log(id)
} else {
res.status(500).send(err);
}
});
});
async function myfunction(publicKey, privateKey) {
await pool.query('INSERT INTO users (publickey, privatekey) VALUES ($1, $2) RETURNING id',
[publicKey, privateKey],
(error, results) => {
if (error) {
throw error;
}
resolve(results.rows[0]['id']);
});
};
However, inside the callback in crypto I get only a Promise, or undefined if I don't use async/await. How can I await myfunction result so I can send back to the user the id?
Several issues here:
await only does something useful when you are awaiting a promise.
pool.query() does not return a promise when you pass it a callback, so your await there is not doing anything useful.
resolve() is not a function that exists outside the context of creating a new promise with new Promise((resolve, reject) => { code here })
throw error inside an asynchronous callback will not do anything useful as there is no way to catch that exception and thus no way to implement any decent error handling. Don't write code that way. When you promisify the function (as shown below), you can then reject the promise and that will offer a way to propagate the error back to the caller.
Your choices for waiting for pool.query() with await here are:
Use the version of your database that natively supports promises and then don't pass a callback to pool.query() so that it returns a promise that tells you when it's complete.
Promisify your own function by wrapping pool.query() in a new promise and call resolve() and reject() appropriately.
Remember, do NOT mix plain callbacks and promise. Instead, promisify any asynchronous functions that use plain callbacks and then do all your logic flow with promises.
Here's a manually promisified version of your myfunction():
function myfunction(publicKey, privateKey) {
return new Promise((resolve, reject) => {
pool.query('INSERT INTO users (publickey, privatekey) VALUES ($1, $2) RETURNING id',
[publicKey, privateKey],
(error, results) => {
if (error) {
reject(error);
return;
}
resolve(results.rows[0]['id']);
});
});
}
crypto.generateKeyPairP = util.promisify(crypto.generateKeyPair);
app.post('/createKeys', async (req, res) => {
try {
const {publicKey, privateKey } = await crypto.generateKeyPairP('rsa');
const id = await myfunction(publicKey.toString('hex'), privateKey.toString('hex'));
console.log(id);
// send your response here, whatever you want it to be
res.send(id);
} catch(e) {
res.status(500).send(e);
}
});
Note that in this implementation, the resolve and reject functions come from the new Promise() - they don't just exist in this air.
But, there is likely a version of your database or an interface in your existing database module where pool.query() can return a promise directly using built-in promise support.
I am using a library which uses RxJS and I am brand new to RxJS so, although I have googled and tried to do my own research I am still a bit lost.
This library has a function that returns an observable. That observable really only returns a single message when subscribed but I need to wait for the data contained within that message before my code can continue.
I have read about chaining observables and all of that but what I am really after is a very simple async/await pattern. I notice there is a .toPromise() on the observable which you can await on, which is fine, however it appears that promise is resolved before the full execution of my next() function which results in a timing issue with the variables it was setting.
Does that sound correct? How can I simply await an observable AND all its side effects such as calls to next()?
What I have ended up doing, which works reliably but seems overkill is to wrap in a promise as demonstrated below. Is there a more succinct way?
const result = await new Promise((resolve, reject) => {
const s = jupyter.sessions.create(this._serverConfig,
{
kernel: {name: this._kernelName},
name: this._sessionName,
path: '/path',
type: 'notebook'
});
s.pipe(take(1)).subscribe({
next(x) {
resolve({sessionId: x.response.id, kernelId: x.response.kernel.id});
},
error(err) {
reject(err);
},
});
});
this._sessionId = result.sessionId;
this._kernelId = result.kernelId;
For completeness, this is the old code that is NOT working. In this code the await completes in a race condition with the next function. That is, randomly both sessionId and kernelId are undefined as per the log message that follows the await
s.subscribe({
next(x) {
console.log(x);
sessionId = x.response.id;
kernelId = x.response.kernel.id;
},
error(err) {
console.error('Got an error from kernel creation:')
console.error(err);
},
})
.add(() => {
console.log('Connection subscription exiting');
});
await s.toPromise();
console.log(`Found session id ${sessionId} and kernel id ${kernelId}`);
I'm not super familiar with promises, but I have a few that I'm implementing within my code and would really like an alert to appear from the client side when the promise is rejected.
Here is the socket I'm calling client side
socket.emit('updateExistingItem', NewItem, (boolean) => {});
NewItem is an object that contains the fields that I want to send.
This calls a promise in my inventory.js file
export const updateExistingItem = (ItemObject) => {
return new Promise(async (resolve, reject) => {
try {
const updateExistingItem = `UPDATE console.inventory_items SET Description = '${(ItemObject.Description).replace(/\'/g, "")}', Location = '${(ItemObject.Location).replace(/\'/g, "")}', ModelNumber = '${(ItemObject.ModelNumber).replace(/\'/g, "")}'`
const response = await db(updateExistingItem, `Updating Item`);
resolve(response);
} catch (e) {
console.log("ERROR inventory.updateExistingItem: " + e);
reject(e);
}
});
};
If a user on the client side puts in information in the NewItem object that doesn't cooperate with the SQL call, I need a way to alert that user that the information wasn't saved/updated. As of right now it's a little misleading to the user, because if they put in something that get's rejected, it looks like it succeeds from their end.
If anyone has solutions that would be fantastic! Thanks.
EDIT
Here is the socket in my sockets.js file
socket.on('updateExistingItem', async (ItemObject, callback) => {
try {
const results = await updateExistingItem(ItemObject);
if (results.affectedRows === 1) {
callback(true);
}
callback(false);
}
catch (error) {}
});
SocketIO being used in my server.js file
var server = http.createServer(app);
const io = socketIO(server);
io.on('connection', (socket) => {
require('./middleware/sockets')(socket);
});
There are 4 different files where these are used so this is why I only put the snippets that actually pertain to this specific call.
Ok so I was able to find a decent solution to this on my own.
In my socket within my sockets.js file I put a callback within the catch method like so
socket.on('updateExistingItem', async (ItemObject, callback) => {
try {
const results = await updateExistingItem(ItemObject);
if (results.affectedRows === 1) {
callback(true);
}
callback(false);
}
catch (error) {
callback(false);
}
});
From there I decided to console log the boolean value that is found in the socket.emit on the client side, and was able to get this callback value in return.
Now it looks something like this
socket.emit('updateExistingItem', NewItem, (boolean) => {
if(boolean){
alert('Successful!');
}else{
alert('UnSuccessful!');
}
});
I now get updates from the client side if this promise gets rejected or resolved.
I'm new to Node/Mongoose and am trying to handle errors correctly in a script to add players to a league. In the below code, explicitly thrown and non-Promise related errors are caught correctly by the .catch() statement, but rejected Promises are not.
For example, trying to pass an invalid userID throws User not found.
But if I test Promise rejection by disconnecting the database, I get the following:
(node:6252) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): MongoNetworkError: failed to connect to server [localhost:27017] on first connect [MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017]
Am I using Promise.all() and .catch() incorrectly somehow?
Just to be clear, I'm trying to work out why the error isn't being handled, not why the error is being thrown.
My script:
const
mongoose = require('mongoose'),
User = require('./models/users'),
League = require('./models/leagues'),
dbUrl = process.env.DBURL || 'mongodb://localhost/predictor';
mongoose.connect(dbUrl, { useNewUrlParser: true });
const addUserToLeague = (userId, leagueId) => {
let foundUser = User.findById(userId);
let foundLeague = League.findById(leagueId);
return Promise.all([foundUser, foundLeague])
.then(arr => {
if(!arr[0]){
throw 'User not found';
}else if(!arr[1]){
throw 'League not found';
}
return arr;
})
.then(arr => {
arr[0].leagueMemberships.push(arr[1]);
arr[1].users.push(arr[0]);
return arr;
})
.then(updatedArr => {
updatedArr[0].save();
updatedArr[1].save();
return updatedArr;
})
.then(updatedArr => { console.log(`User ${updatedArr[0]._id} added to league ${updatedArr[1]._id}`) })
.catch(err => { console.log('Error:', err) });
};
addUserToLeague(process.argv[2], process.argv[3]); // Needs 2 args: User ID and League ID
As Bergi pointed out, the error would appear to be from connect, which returns a promise that you're not handling at all — including not waiting for it to finish. So at a minimum, you need to handle that:
const connectionPromise = mongoose.connect(dbUrl, { useNewUrlParser: true })
.catch(error => {
// Handle connection error
});
Then in addUserToLeague:
const addUserToLeague = (userId, leagueId) => {
return connectionPromise.then(connection => {
// ...logic here
});
};
...but, I question whether you should be connecting when the module is loaded like that, rather than passing a connection into addUserToLeague.
Aside from that, the actual use of Promise.all is okay, but:
One hopes that findById doesn't resolve the promise with a falsy value if the item isn't found, so that whole first then handler seems unnecessary.
Presumably save returns a promise. You're not handling rejection or waiting for resolution of those if so.
I'd use destructuring to avoid arr[0] and arr[1], as it's easy to forget the order.
There's no reason for the then handler with the push calls to be separate from the then handler doing the saving.
addUserToLeague should return the result of the promise chain, so that code calling it A) Knows when it's finished, and B) Knows when it fails.
Errors shouldn't be handled in addUserToLeague; instead, handle them in its caller.
There's also the issue that the data is denormalized: You're storing the membership information in both the user object and the league object. Maybe that's relatively normal in document databases (I wouldn't know); in an RDBMS you'd store the information in a single place. The reason is clear from the code in addUserToLeague: What if saving the user succeeds but saving the league fails? Then the user object says it's a member of a league the league object doesn't say it's a member of. There's also the problem that since it's stored in two places, even if nothing goes wrong, for a brief period one of the (the user or the league) will have been saved but the other won't have been. Both are integrity problems. If you can normalize it to storing this information in one place, that would be good. If you can't, you need to update the code so that it saves one of them, waits for that to succeed, saves the other, and if that fails attempts to undo the change to the first.
Something like this (I don't attempt to address the normalization issue here, that's a big picture thing):
const
mongoose = require('mongoose'),
User = require('./models/users'),
League = require('./models/leagues'),
dbUrl = process.env.DBURL || 'mongodb://localhost/predictor';
const addUserToLeague = (connection, userId, leagueId) => {
return Promise.all([
User.findById(userId),
League.findById(leagueId)
])
.then(([user, league]) => {
user.leagueMemberships.push(league);
league.users.push(user);
return Promise.all([user.save(), league.save()]);
})
.then((([user, league]) => {
console.log(`User ${user._id} added to league ${league._id}`);
});
};
mongoose.connect(dbUrl, { useNewUrlParser: true })
.then(connection => addUserToLeague(connection, process.argv[2], process.argv[3]) // Needs 2 args: User ID and League ID
.catch(error => {
// Handle/report error
});
If you're using any recent verson of Node, you can use an async function:
const
mongoose = require('mongoose'),
User = require('./models/users'),
League = require('./models/leagues'),
dbUrl = process.env.DBURL || 'mongodb://localhost/predictor';
const addUserToLeague = async (connection, userId, leagueId) => {
let [user, league] = await Promise.all([
User.findById(userId),
League.findById(leagueId)
]);
user.leagueMemberships.push(league);
league.users.push(user);
[user, league] = await Promise.all([user.save(), league.save()]);
console.log(`User ${user._id} added to league ${league._id}`);
};
mongoose.connect(dbUrl, { useNewUrlParser: true })
.then(connection => addUserToLeague(connection, process.argv[2], process.argv[3]) // Needs 2 args: User ID and League ID
.catch(error => {
// Handle/report error
});
So I have an Express app that uses middleware to parse JSON POST requests and then populate a req.body object. Then I have a promise chain that validates the data against a schema using Joi, and then stores it in a database.
What I would like to do is check if an error was thrown after one of these processes, handle it appropriately by sending a status code, then COMPLETELY ABORT the promise chain. I feel like there should be some EXTREMELY CLEAN AND SIMPLE way to do this, (perhaps some sort of break statement?) but I can't find it anywhere. Here is my code. I left comments showing where I hope to abort the promise chain.
const joi = require("joi");
const createUserSchema = joi.object().keys({
username: joi.string().alphanum().min(4).max(30).required(),
password: joi.string().alphanum().min(2).max(30).required(),
});
//Here begins my promise chain
app.post("/createUser", (req, res) => {
//validate javascript object against the createUserSchema before storing in database
createUserSchema.validate(req.body)
.catch(validationError => {
res.sendStatus(400);
//CLEANLY ABORT the promise chain here
})
.then(validatedUser => {
//accepts a hash of inputs and stores it in a database
return createUser({
username: validatedUser.username,
password: validatedUser.password
})
.catch(error => {
res.sendStatus(500);
//CLEANLY ABORT the promise chain here
})
//Only now, if both promises are resolved do I send status 200
.then(() => {
res.sendStatus(200);
}
)
});
You can't abort a promise chain in the middle. It's going to either call a .then() or a .catch() later in the chain (assuming there are both and assuming your promises resolve or reject).
Usually, the way you handle this is you put one .catch() at the end of the chain and it examines the type of error and takes appropriate action. You don't handle the error earlier in the chain. You let the last .catch() handle things.
Here's what I would suggest:
// helper function
function err(status, msg) {
let obj = new Error(msg);
obj.status = status;
return obj;
}
//Here begins my promise chain
app.post("/createUser", (req, res) => {
//validate javascript object against the createUserSchema before storing in database
createUserSchema.validate(req.body).catch(validationError => {
throw err("validateError", 400)
}).then(validatedUser => {
//accepts a hash of inputs and stores it in a database
return createUser({
username: validatedUser.username,
password: validatedUser.password
}).catch(err => {
throw err("createUserError", 500);
});
}).then(() => {
// success
res.sendStatus(200);
}).catch(error => {
console.log(error);
if (error && error.status) {
res.sendStatus(error.status);
} else {
// no specific error status specified
res.sendStatus(500);
}
});
});
This has several advantages:
Any error propagates to the last .catch() at the end of the chain where it is logged and an appropriate status is sent in just one place in the code.
Success is handled in just one place where that status is sent.
This is infinitely extensible to more links in the chain. If you have more operations that can have errors, they can "abort" the rest of the chain (except the last .catch() by just rejecting with an appropriate error object).
This is somewhat analogous to the design practice of not having lots of return value statements all over your function, but rather accumulating the result and then returning it at the end which some people consider a good practice for a complicated function.
When debugging you can set breakpoints in one .then() and one .catch() to see the final resolution of the promise chain since the whole chain goes through either the last .then() or the last .catch().
.catch returns a resolved Promise by default. You want a rejected Promsise. So, you should return a rejected promise from inside the .catch, so that future .thens won't execute:
.catch(validationError => {
res.sendStatus(400);
return Promise.reject();
})
But note that this will result in a console warning:
Uncaught (in promise) ...
So it would be nice to add another .catch to the end, to suppress the error (as well as catch any other errors that come along):
const resolveAfterMs = ms => new Promise(res => setTimeout(() => {
console.log('resolving');
res();
}), ms);
console.log('start');
resolveAfterMs(500)
.then(() => {
console.log('throwing');
throw new Error();
})
.catch(() => {
console.log('handling error');
return Promise.reject();
})
.then(() => {
console.log('This .then should never execute');
})
.catch(() => void 0);
If you want to avoid all future .thens and future .catches, I suppose you could return a Promise that never resolves, though that doesn't really sound like a sign of a well-designed codebase:
const resolveAfterMs = ms => new Promise(res => setTimeout(() => {
console.log('resolving');
res();
}), ms);
console.log('start');
resolveAfterMs(500)
.then(() => {
console.log('throwing');
throw new Error();
})
.catch(() => {
console.log('handling error');
return new Promise(() => void 0);
})
.then(() => {
console.log('This .then should never execute');
})
.catch(() => {
console.log('final catch');
});
A cleaner solution for what you are trying to accomplish might be to use express-validation, which is a simple wrapper around joi that provides you with express middleware for validation of the body, params, query, headers and cookies of an express request based on your Joi schema.
That way, you could simply handle any Joi validation errors thrown by the middleware within your "generic" express error handler, with something like:
const ev = require('express-validation');
app.use(function (err, req, res, next) {
// specific for validation errors
if (err instanceof ev.ValidationError)
return res.status(err.status).json(err);
...
...
...
}
If you don't want to use the express-validation package, you could write your own simple middleware that does more or less the same thing, as described here (see example here).
One strategy is to separate your error handling in subpromises which have their individual error handling. If you throw an error from them, you'll bypass the main promise chain.
Something like:
return Promise.resolve().then(() => {
return createUserSchema.validate(req.body)
.catch(validationError => {
res.sendStatus(400);
throw 'abort';
});
}).then(validatedUser => {
// if an error was thrown before, this code won't be executed
// accepts a hash of inputs and stores it in a database
return createUser({
username: validatedUser.username,
password: validatedUser.password
}).catch(error => {
// if an error was previously thrown from `createUserSchema.validate`
// this code won't execute
res.sendStatus(500);
throw 'abort';
});
}).then(() => {
// can put in even more code here
}).then(() => {
// it was not aborted
res.sendStatus(200);
}).catch(() => {
// it was aborted
});
You can skip the Promise.resolve().then() wrapping, but it's included for illustrative purposes of the general pattern of subdividing each task and its error handling.