I'm new to node.js and I'm having a hard time trying to understand the concept of event-based async programming.
I'm implementing a restful API web service, so consider the following simple (synchronous!) API method addStuff(), which inserts stuff to an elasticsearch db:
var client = new elasticsearch.Client({ host: 'localhost:9200' });
function indexStuff(stuff) {
return client.index({
index: 'test_idx',
type: 'test',
id: stuff.id,
body: stuff
});
}
function addStuff(req, res, next) {
let stuff = processRequest(req);
indexStuff(stuff).then(
function (body) {
return true;
},
function (error) {
res.status(error.status).send({ message: error.message });
}
);
}
So far, so good.
Now during testing I wanted to avoid inserting already existing stuff to the db.
So I'd like to add something like:
function stuffAlreadyInDB(id) {
... // returns true/false
}
function addStuff(req, res, next) {
if (stuffAlreadyInDB(req.id))
{
res.status(409).send({ message: 'stuff with id ' + req.id + ' already in DB' });
return;
}
var stuff = processRequest(req);
...
}
Unfortunately, the call to the elasticsearch db is asyncronous, which means, I can't just return a boolean in a sync function. Instead, I have to refactor the whole shabang to something (argueably less easy to read) like this:
function getStuffByID(id) {
return client.get({
id: id,
index: 'test_idx',
type: 'test',
ignore: 404
});
}
function addStuff(req, res, next) {
getStuffByID(req.id).then(
function(resp) {
if (resp.found) {
res.status(409).send({ message: 'stuff with id ' + req.id + ' already in DB' });
return;
}
else {
var stuff = processRequest(req);
indexStuff(stuff).then(
function (body) {
res.writeHead(200, {'Content-Type': 'application/json' });
res.end();
},
function (error) {
res.status(error.status).send({ message: error.message });
}
);
}
},
function(error) {
res.status(error.status).send({ message: error.message });
}
);
}
At least, I haven't found any better solution. I tried to find out how to make the async call to the db a sync call, but basically everybody was saying: just don't do it.
So how am I supposed to do it right if I don't want to refactor everything and back-factor it when I finished testing and don't need this extra db check anymore?
Oh... and if you downvote my question: leave a comment why you do so.
Because I have the feeling that many people struggle with this issue, but I haven't found a satisfying answer yet.
you could use async\await syntax to make your code readable.
for example you could do this:
async function getStuffById(){
//return true or false; }
and in the "add stuff" function you could write:
if ( await getStuffById() ){
//do some more stuff }
please notice that you have to make "add stuff" async as well in order to use await syntax.
more on async \ await can be found here
Related
I made a controller on nestjs as below.
#Post()
public async addConfig(#Res() res, #Body() createConfigDto: CreateConfigDto) {
let config = null;
try {
config = await this.configService.create(createConfigDto);
} catch (err) {
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
status: 500,
message: 'Error: Config not created!',
});
}
if (!config) {
return res.status(HttpStatus.NOT_FOUND).json({
status: 404,
message: 'Not Found',
});
}
return res.status(HttpStatus.OK).json({
message: 'Config has been created successfully',
config,
});
}
And there is a service in other file.
public async create(createConfigDto: CreateConfigDto): Promise<IConfig> {
do something 1
do something 2
do something 3
return result;
}
If an internal server error occurs among "do somehting 1,2 or 3" when I request to this api, it will gives me response 500.
But I want to know in which line the error happend.
Therefore I made catch line function.
const getStackTrace = () => {
const obj = {};
Error.captureStackTrace(obj, getStackTrace);
return obj.stack;
};
And I wrap the code with this function, like this
public async create(createConfigDto: CreateConfigDto): Promise<IConfig> {
try{
do something 1
}catch(error){
console.log(error)
getTrace()
}
try{
do something 2
}catch(error){
console.log(error)
getTrace()
}
try{
do something 3
}catch(error){
console.log(error)
getTrace()
}
return result;
}
But the problem is the service code will grow a lot with this trace function.
I want to know whether there is more efficient way avoiding duplicate code using nestJs for this case.
I think interceptor or execption filter help this.
But I don't know how to code for this case , since I 'm about to start using nestjs.
Thank you for reading my question.
I am using the imap-simple NPM package to check emails, and I am having trouble getting the insert to work properly.
I have already read through this page: https://guide.meteor.com/using-npm-packages.html#async-callbacks - and I have tried the suggestions but none of them are working!
I've also simplified the code a bit just to try to get it working, but still have no luck.
The problem should be very easy to reproduce - meteor npm install imap-simple, throw the above code on the server, add some email credentials, and call the method.
Here is my code:
var imaps = require('imap-simple');
var config = {
imap: {
user: '<removed>',
password: '<removed>',
host: 'imap.gmail.com',
port: 993,
tls: true,
authTimeout: 3000
}
};
Meteor.methods({
api_connectEmail: function () {
console.log('Received call to connect email');
imaps.connect(config).then(function (connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = [
'UNSEEN'
];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
markSeen: true
};
return connection.search(searchCriteria, fetchOptions).then(function (results) {
results.map(function (res) {
var subject = res.parts.filter(function (part) {return part.which === 'HEADER';})[0].body.subject[0];
console.log("Subject: " + subject);
// insert
var attributes = {
subject: subject
};
console.log("Attempting to insert to collection...");
var newData = TempEmailCollection.insert(attributes);
console.log("New Database Entry ID: " + newData);
});
});
});
})
}
});
The console.log with the subject is working. The insert is not working. No error, no console.log post insert, nothing.
I've tried both strategies recommended in the guide, neither work.
The problem is that you are calling a Meteor function inside asynchronously called Promise handlers.
However, all Meteor functions that are called on the server have to run in a fiber.
Meteor actually throws an error in this case but you are ignoring it because you haven't specified catch functions for the Promises.
Consider the following simplified example (it just connects to the server and tries to insert a new document):
import { Meteor } from 'meteor/meteor';
import imaps from 'imap-simple';
const Storage = new Mongo.Collection('storage');
const config = {
imap: {
…
}
};
Meteor.methods({
connect() {
console.log('Method called');
imaps.connect(config).then(function(connection) {
console.log('Connected');
Storage.insert({
value: 'success'
});
console.log('Document inserted');
})
.catch(function(err) {
console.error(err);
});
}
});
The following message will arrive in the catch function:
[Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.]
You could do something like this to wrap the insert call:
Meteor.methods({
connect() {
console.log('Method called');
const insert = Meteor.bindEnvironment(function() {
Storage.insert({
value: 'success'
});
});
imaps.connect(config).then(function(connection) {
console.log('Connected');
insert();
console.log('Document inserted');
})
.catch(function(err) {
console.error(err);
});
}
});
Then the document will be inserted as expected.
I am trying to write a function that add or edit some fields on a User object.
The problem come when I try to save the user, if I use user.save, the Promise is rejected with error 206 UserCannotBeAlteredWithoutSessionError.
However, if I get the session id (and documentation about that is scarce), the promise never get resolve, nor rejected. The app seems to just jump to the callback.
My function:
function update(user, callback) {
let query = new Parse.Query(Parse.User);
query.equalTo("username", user.email);
query.find().then(
(users) => {
if(users.length === 0) {
callback('Non existent user');
} else {
let user = users[0];
// user.set('some', 'thing');
console.log('save');
user.save(/*{
sessionToken: user.getSessionToken()
}*/).then(
(test) => {
console.log('OK - ' + test);
callback();
}, (err) => {
console.log('ERR- ' + require('util').inspect(err));
// console.log(callback.toString());
callback(error.message);
}
);
}
},
(error) => {
callback(error.message);
}
);
}
Called with:
var async = require('async'),
baas = require('./baas.js');
async.waterfall([
(callback) => {
callback(null, {
email: 'user#test.com',
password: 'password'
});
},
(user, callback) => {
console.log('connect');
baas.connect(() => { //Initialize the connection to Parse, and declare use of masterKey
callback(null, user);
});
},
(user, callback) => {
console.log('update');
baas.update(user, (err) => {
callback(err);
});
}
], (err) => {
console.log('Error: ' + err);
});
The logs become:
Without session token:
connect
update
save
ERR- ParseError { code: 206, message: 'cannot modify user sA20iPbC1i' }
With session token:
connect
update
save
I do not understand how it is possible that the promise just callback without printing anything, nor why no error are raised anywhere.
Edit:
Following #user866762 advice, I tried to replace the query with Parse.User.logIn and use the resulting User object.
While this solution give me a sessionToken, the end result is the same, parse crash if I don t provide the session token, or give me a error if I do.
According to the Parse Dev guide:
...you are not able to invoke any of the save or delete methods unless the Parse.User was obtained using an authenticated method, like logIn or signUp.
You might also try becoming the user before saving, but I have my doubts that will work.
When you're "get[ting] the session id" my guess is that you're really breaking something. Either Parse is having a heart attack at you asking for the session token, or when you're passing it in save you're causing something there to explode.
I'm using Express and Handlebars to display a value set by the user and stored in the database.
Handlebars is set up to display the value "{{userMotto}}".
Express does the following:
function isUserAuthenticated(req, res, next) {
if (!req.user) {
res.render('index', {
user: req.user
});
} else {
currentUser = req.user.username;
userMottoCaught = queryDatabase("motto", currentUser);
next();
}
}
I want it to set the value of "userMottoCaught" to whatever it finds in the database. The query itself is this:
function queryDatabase(dbCollection, dbUID) {
this.dbCollection = dbCollection;
this.dbUID = dbUID;
return MongoClient.connectAsync(hiddenkeys.mongodbUri)
.then(function(db) {
return db.collection(dbCollection).findOneAsync({
_id: dbUID
});
})
.then(function(item) {
console.log("Found: ");
console.log(item);
return dbQueryResult;
})
.catch(function(err) {
//err
});
}
The problem is that I cannot for the life of me get the dbQueryResult out and return it to function queryDatabase itself. Probably because it's being returned to a sub function right now instead of the main function, I think. I highly suspect this can be easily resolved but I'm just at a loss on how to fix this. I am using Bluebird here to see if I could solve this with promises, but I'm not sure this is the right route either. I've also looked into callbacks but I cannot for the life of me figure out how to apply either concept to my code to solve my problem.
Later on when I render the page I do this to render it on the page:
router.get('/', isUserAuthenticated, function(req, res) {
res.render('dashboard', {
user: req.user,
userMotto: userMottoCaught
});
});
Currently this yields on the page: "Motto: [object Promise]", because I haven't returned the proper value to the main function.
Is there anyone out there with some wise words?
Cheers,
Dean
i think you need to make a callback here
function isUserAuthenticated(req, res, next) {
if (!req.user) {
res.render('index', {
user: req.user
});
} else {
currentUser = req.user.username;
userMottoCaught = queryDatabase("motto", currentUser,function(err,data){
userMottoCaught = data
next();
});
}
}
and the definition of queryDatabase should look like
function queryDatabase(dbCollection, dbUID,cb) {
this.dbCollection = dbCollection;
this.dbUID = dbUID;
return MongoClient.connectAsync(hiddenkeys.mongodbUri)
.then(function(db) {
return db.collection(dbCollection).findOneAsync({
_id: dbUID
});
})
.then(function(item) {
console.log("Found: ");
console.log(item);
dbQueryResult = JSON.stringify(item.motto);
cb(null,dbQueryResult)
})
.catch(function(err) {
//err
cb(err);
});
}
I need to return the search results from Mongoose.findOne to variable
results = Lang.findOne({page: params.page,lang: params.lang, param: params.param}, function(err, lang) {
if( err || !lang) {
console.log("No translation!");
} else {
return lang.trans;
};
}
Is there a way to do this? I tried several hours to find a solution with async. functions and nothing ...
I also found articles where it was said that this is impossible, but is there any alternative for realize this?
It's really important to me because I need this to my multilingual project, I need to get the translation:
res.render('index',{titleGen : req.__({page:'home', lang:req.locale, param:'hello'})});
Here is the solution:
exports.getLang = getLang = function(params,callback){
console.log('received: '+params.page+' + '+params.lang+' + '+params.param);
Lang.findOne({page: params.page, lang: params.lang, param: params.param},function(err, lang){
if(err)
console.log(err)
else{
callback(lang.trans);
}
});
}
and routing:
router.get('/', function(req, res) {
req.__({page:'home', lang:req.locale, param:'hello'},function(text){
res.render('index',{titleGen : text });
})
});