This question already has answers here:
How does the "this" keyword work, and when should it be used?
(22 answers)
Closed 7 years ago.
I'm using Sails.js framework and its great Model's feature, also I'm using the async version it uses as its dependency.
So, in order of explaining a practical scenario: get all comments on songs sung by an artist. I should first query the songs and then, query against comments.
This why I'm using async module, in particular its waterfall function. But as all this code is placed at a Model file, where this refers to the model itself, I got one big doubt:
Will this always refer to the model even when present inside async functions?
This is a code example of what I'm doing:
module.exports = {
connection: 'seasonDB',
attributes: {
reachesMaxFavoriteTeam: function (team) {
var results [];
async.waterfall([
// Get favorite team
function (cb) {
var userTeam = this.userTeam;
UserTeam.findOne({_id: userTeam}, function (err, foundUserTeam) {
if (err)
cb(err,null);
var user = foundUserTeam.user;
User.find({_id: user}, {_id:false, fanOf: true}, function (err, fanOf) {
if (err)
cb(err,null);
cb(null,fanOf.name);
});
});
},
// Check if it reaches a max favorite team error
function (favoriteTeam,cb) {
// If player to be added is from favorite team, it counts.
favoriteCount = team === favoriteTeam ? 1: 0;
// Check if any of added players are from favorite team too.
_.forEach(this.players, function (player) {
var playerTeam = player.team.name;
if (playerTeam === favoriteTeam)
favoriteCount++;
});
return favoriteCount > process.env.CONDITION;
}]);
}
};
So, for example, at the first function in waterfall series I got: var userTeam = this.userTeam;, will this work as expected? Should I take care of something in case of other nested functions?
Why don't you use Promises in those queries will make it so much easier to work with. I wouldn't use async, using Promises should do the job.
The underlying ORM module in Sails is Waterline. You can refer to Chaining waterline calls with Promises for an example or the GitHub page https://github.com/balderdashy/waterline which shows the below example,
User.findOne()
.where({ id: 2 })
.then(function(user){
var comments = Comment.find({userId: user.id}).then(function(comments){
return comments;
});
return [user.id, user.friendsList, comments];
}).spread(function(userId, friendsList, comments){
// Promises are awesome!
}).catch(function(err){
// An error occurred
})
Waterline-docs is also helpful for reference: https://github.com/balderdashy/waterline-docs
Related
This answer to a similar question does a great job at explaining how fastify-plugin works and what it does. After reading the explanation, I still have a question remaining; how is this different from a normal function call instead of using the .register() method?
To clarify with an example, how are the two approaches below different from each other:
const app = fastify();
// Register a fastify-plugin that decorates app
const myPlugin = fp((app: FastifyInstance) => {
app.decorate('example', 10);
});
app.register(myPlugin);
// Just decorate the app directly
const decorateApp = (app: FastifyInstance) => {
app.decorate('example', 10);
};
decorateApp(app);
By writing a decorateApp function you are creating your own "API" to load your application.
That said, the first burden you will face soon is sync or async:
decorateApp is a sync function
decorateAppAsync within an async function
For example, you need to preload something from the database before you can start your application.
const decorateApp = (app) => {
app.register(require('#fastify/mongodb'))
};
const businessLogic = async (app) => {
const data = await app.mongo.db.collection('data').find({}).toArray()
}
decorateApp(app)
businessLogic(app) // whoops: it is async
In this example you need to change a lot of code:
the decorateApp function must be async
the mongodb registration must be awaited
the main code that loads the application must be async
Instead, by using the fastify's approach, you need to update only the plugin that loads the database:
const applicationConfigPlugin = fp(
+ async function (fastify) {
- function (fastify, opts, next) {
- app.register(require('#fastify/mongodb'))
- next()
+ await app.register(require('#fastify/mongodb'))
}
)
PS: note that fastify-plugin example code misses the next callback since it is a sync function.
The next bad pattern will be high hidden coupling between functions.
Every application needs a config. Usually, the fastify instance is decorated with it.
So, you will have something like:
decorateAppWithConfig(app);
decorateAppWithSomethingElse(app);
Now, decorateAppWithSomethingElse will need to know that it is loaded after decorateAppWithConfig.
Instead, by using the fastify-plugin, you can write:
const applicationConfigPlugin = fp(
async function (fastify) {
fastify.decorate('config', 42);
},
{
name: 'my-app-config',
}
)
const applicationBusinessLogic = fp(
async function (fastify) {
// ...
},
{
name: 'my-app-business-logic',
dependencies: ['my-app-config']
}
)
// note that the WRONG order of the plugins
app.register(applicationBusinessLogic);
app.register(applicationConfigPlugin);
Now, you will get a nice error, instead of a Cannot read properties of undefined when the config decorator is missing:
AssertionError [ERR_ASSERTION]: The dependency 'my-app-config' of plugin 'my-app-business-logic' is not registered
So, basically writing a series of functions that use/decorate the fastify instance is doable but it adds
a new convention to your code that will have to manage the loading of the plugins.
This job is already implemented by fastify and the fastify-plugin adds many validation checks to it.
So, by considering the question's example: there is no difference, but using that approach to a bigger application
will lead to a more complex code:
sync/async loading functions
poor error messages
hidden dependencies instead of explicit ones
This question already has answers here:
How to avoid long nesting of asynchronous functions in Node.js
(23 answers)
Closed 6 years ago.
I'm new in js. In my project I'm using mysql and node js.
Somewhere in controller I need to get some data from differnet models. In php it looks like
function some() {
$user = $user->getOne($id);
$photos = $photos->getOne($user->id);
$posts = $post($user->id, $photo->uid)
}
and I have all this variables in one scope
in node js result of model is async, so it's look like nestings callbacks.
Short example
UserModel.findbyid(result.user_id, function (err, user_data) {
PhotoModel.GetVoteCount(user_data.id, result.id, function (res_count) {
PhotoModel.getWinners(function (err, winners_ar) {
PhotoModel.getweekusers(1, function (result_week) {
response.render('one.twig', {
view_user: request.user,
image: result,
p_user: user_data,
count: res_count,
winners: winners_ar,
week_users: result_week['photos']
});
});
})
});
});
so I have nested callbacks, I feel it's not right way to code, can you explain best practices?
You have two options :
Use async module (async)
Use a library which returns promises (like promise-mysql).
You can take a look at async module
This simplifies what you call "callback hell" through some functions that helps create flows in async code.
Specifically to your case - async.waterfall will do the trick
Use promises,or libraries that will help you use promises.
I'm working with a Lightswitch 2015 application for tracking customer satisfaction interviews. I'm trying to create a function that will create a new interview, populate a few items, then save it to the database, and open it for editing in a new window. I'm struggling with understanding the behavior of promises in this context.
I have these three blocs of code:
1)
myapp.Interviews_Management.AddInterview_Tap_execute = function (screen)
{
var NewInterview = screen.Interviews.addNew();
NewInterview.c_Date = screen.InterviewDate;
NewInterview.Participant = screen.Interviewee;
NewInterview.Location = screen.InterviewFocusLocation;
screen.closePopup()
.then(() =>
{
myapp.applyChanges()
.then(() =>{ myapp.showInterview_AddEdit(NewInterview); });
}, (error) =>{ msls.showMessageBox(error.toString()); });
};
2)
myapp.Interviews_Management.AddInterview_Tap_execute = function (screen)
{
var NewInterview = screen.Interviews.addNew();
NewInterview.c_Date = screen.InterviewDate;
NewInterview.Participant = screen.Interviewee;
NewInterview.Location = screen.InterviewFocusLocation;
screen.closePopup()
.then(myapp.applyChanges()
.then(myapp.showInterview_AddEdit(NewInterview),
(error) =>{ msls.showMessageBox(error.toString()); })
);
};
3)
myapp.Interviews_Management.AddInterview_Tap_execute = function (screen)
{
var NewInterview = screen.Interviews.addNew();
NewInterview.c_Date = screen.InterviewDate;
NewInterview.Participant = screen.Interviewee;
NewInterview.Location = screen.InterviewFocusLocation;
screen.closePopup()
.then(myapp.applyChanges())
.then(myapp.showInterview_AddEdit(NewInterview),
(error) =>{ msls.showMessageBox(error.toString()); });
};
1 works as expected; that is, creates a new interview, populates the fields, and opens an edit window. However, I'm concerned that errors thrown from inside the lambda function(s) won't be properly passed to the external context.
2 and 3 both create a new interview and populate the fields, but then throws an error saying, "This action cannot be taken while screen is navigating.", which seems to suggest it is not waiting for each promise to fulfill before attempting to execute the next one. 3 also seems like perhaps it is wrapping promises in other promises, which may again lead to not properly passing any thrown errors outward (and, I hear, is a pretty common anti-pattern for people struggling with understanding promises?).
Can someone help me understand what exactly is going on here? I've been struggling generally with proper best practice in nesting promises or using a series of promises; any help with proper way of handling this would be much appreciated!
You always need to pass a callback function to then, not the result of calling it immediately.
You want
screen.closePopup()
.then(myapp.applyChanges) // or () => myapp.applyChanges()
.then(() => myapp.showInterview_AddEdit(NewInterview)),
.catch(error => { msls.showMessageBox(error.toString()); });
For why that catch is necessary (better than using the second then parameter), see When is .then(success, fail) considered an antipattern for promises?.
I've been getting to grips with node and node-sqlite3 and need to build a report up based on a number of queries:
var db = require('./db');
module.exports = {
getActivity : function (user_id, done) {
var report = {};
db.get('SELECT * FROM warehouse WHERE user_id = ?', user_id, function (err, warehouse) {
report.warehouse = warehouse;
db.all('SELECT * FROM shops WHERE warehouse_id = ?', report.warehouse.id, function (err, shops) {
report.shops = shops;
return done(report);
});
});
}
};
My goal was to be able to generate a report from a route and serialize it as a JSON response. Here's how my route looks:
app.get('/api/hello',
auth.check,
function(req, res) {
hello.getActivity(1, function (data) {
res.send(data);
});
});
I will most likely have more queries to include in this report and thus more nested callbacks. What options do I have to avoid this? I'm familiar with promises etc but node-sqlite doesn't have anything built in for cleaning this stuff up. Maybe I am using it incorrectly?
Last of all, I am passing in a 'done' callback from the route. Maybe this is the node way of doing things but it would be great if I could just simply return the report once it's generated, without the callback. Is there a better pattern for this?
Any help appreciated!
I have a report engine built on node that has the same issues of multiple queries. To keep thinks clean, I use async, which is an awesome control flow library:
https://github.com/caolan/async#series
You will want to look at the async.series. It keeps your code a little cleaner than tons of embedded functions.
NOTE: You will need to create a reference to variables you need to access from one step to the next outside of the async.series context. For exaple I use the var one in function for two:
//keep context to shared values outside of the async function
var one,
two;
async.series([
function(callback){
// do some stuff ...
one = 'one';
callback(null, one);
},
function(callback){
//!access value from previous step
two = one + one;
callback(null, two);
}
],
// optional callback
function(err, results){
// results is now equal to ['one', 'oneone']
});
I am struggling a little with how to call asynchronous functions in a serial manner. In particular, functions which incorporate mongoose calls to the database. I have a class definition which includes two methods: (MovieFile.exists) checks if a certain record exists in the database; (MovieFile.save) saves a record in the database. Ideally I want to be able to save the record (MovieFile.save()) after confirming whether or not it exists in the database (MovieFile.exists()).
Ideally I want to do something like this:
// Create instance of MovieFile object.
var movieFile = new MovieFile(mongoose);
if (movieFile.exists() === false) {
movieFile.save();
}
Unfortunately the asynchronous nature on mongoose makes this not possible. I have been playing around with Step and Async control flow frameworks. Unfortunately I just can't get my head around how to use such frameworks in this case. I would be grateful if someone could tell me how to put the above code into either Step or Async. I think the issue is that the asynchronous mongoose calls are themselves embedded within a method (See: MovieFile.exists and MovieFile.save). I realise that use of such a framework may be overkill in this example, but I am trying to use this as a learning exercise.
function MovieFile(mongoose) {
var Movie = mongoose.model('Movie');
this.exists = function() {
// Confirm necessary object variables have been set.
if (typeof this.originalFileName === 'undefined') {
throw error = new Error('Variable originalFilename has not been set for MovieFile.');
}
if (typeof this.fileSize !== 'number') {
throw error = new Error('Variable originalFilename has not been set for MovieFile.');
}
// Check database for existing record.
Movie
.find({ originalFileName: this.originalFileName, size: this.fileSize })
.exec(function(error, results) {
if (error) {
throw error;
}
else if (results.length === 0) {
return false;
}
else if (results.length === 1) {
return true;
}
else {
throw error = new Error('More than one record for this movie record exists.');
}
});
};
this.save = function() {
// save movie to database.
var values = {
name: this.getName(),
machineFileName: this.getMachineFileName(),
originalFileName: this.getName(),
size: this.getFileSize(),
};
var movie = new Movie(values);
movie.save(function(error, data) {
if (error) {
console.log(error);
}
else {
console.log('Movie saved.');
}
});
};
};
You have to pass a callback to your exists function, wich will return the boolean true or false.
Exemple :
this.exists = function(callback) {
// your code
callback(true);
});
Then :
movieFile.exists(function(exists) {
if (!exists) boomovieFile.save();
});
As you said, this is due to the asynchronous mongoose calls. So, you will have to replace your return statements by callbacks.
I've never used Step or Async control flow frameworks. But I think, you don't need to rely on those modules for this kind of simple case. In my opinion, you should only consider using those modules when you will have to deal with a lot of callbacks.
Edit
As I'm new to control flow frameworks, I've found this article which describes well the concept and shows how to build your own. That's great for learning purposes.
This article will give you an example on how to manage a queue using the async module.
This post list some other control flow modules, with few examples.
The first answer also show how to avoid nested callbacks, by decoupling on small functions. This is how I personnaly manage my apps, which works quite well in my case.
But I would be curious to hear more about control flow techniques, so let me know if you found great ressources.