Node.js - Async problem

116 views
Skip to first unread message

Дмитрий Папка

unread,
Aug 20, 2014, 9:26:14 AM8/20/14
to nod...@googlegroups.com
Hello, everyone!
I am new to Node.Js and not very well understanding the mecanics of callbacks.

I have a MongoDB database installed and running. I have a 'users' collection. In my Node.js (Express) application I want to display a list of stored users.
I am doing the following experiment:

var express = require('express');
var router = express.Router();
var userRepository = require('../repositories/user_repository.js');

router.get('/:long?', function(req, res) {
    var long = req.param('long');
    long = !!long;
    userRepository.getAllUsers(function(users) {
        res.render('index', {
            users: users
        });
    }, long);
});


It's a handler for my http://website.com/ URL, which may have an optional URL parameter 'long' I am passing it to my userRepository.getAllUsers method.

userRepository looks like this:

var db = require('../context').db;

var userRepository = {
    getAllUsers: function(callback, long) {
        var now = Date.now();
        var then = now + 5000;
        if (long) {
             while (Date.now() < then) {}
        }
        db.users.find({}, function(err, users) {
            callback(users);
        });
    }
};

module.exports = userRepository;


context.js contains only mongoDB settings (username, password, url).

The idea is the following:

If user enters url:
http://website.com/, then 'long' parameter will be false and userRepository will just get user list from database and will render to my view.

If user enters url:
http://website.com/?long=something, then 'long' parameter will be true and before extracting user list, getAllUsers will 'sleep' for 5 seconds.

With this approach I am trying to simulate a long-time operation (for example, if my users colletion has a lot of documents and we are trying to get them all).

If I am openning:
http://website.com/, it loads immediately.
If I am oppening:
http://website.com/?long=something, it loads after ~ 5 seconds as expected.
Now. If I am openning
http://website.com/?long=something and right after that http://website.com/ - they both are loading 5 seconds (second request is waiting until the first one will end).
That means I am doing something wrong, because requests are handled not asyncroniously.
What did I do wrong?

Aria Stewart

unread,
Aug 20, 2014, 5:38:09 PM8/20/14
to nod...@googlegroups.com
On Aug 20, 2014, at 9:26 AM, Дмитрий Папка <dmitry...@gmail.com> wrote:

Hello, everyone!
I am new to Node.Js and not very well understanding the mecanics of callbacks.


[snip]

        if (long) {
             while (Date.now() < then) {}
        }
        db.users.find({}, function(err, users) {
            callback(users);
        });


[snip]

If I am openning: http://website.com/, it loads immediately.
If I am oppening:
http://website.com/?long=something, it loads after ~ 5 seconds as expected.
Now. If I am openning
http://website.com/?long=something and right after that http://website.com/ - they both are loading 5 seconds (second request is waiting until the first one will end).
That means I am doing something wrong, because requests are handled not asyncroniously.
What did I do wrong?

The while loop is blocking -- it spins, doing useless work and NOTHING else until it expires. Your entire node process is hung during this time, so both requests "wait".

Delaying with:

 setTimeout(function() {
db.users.find({}, function(err, users) {
       callback(users);
 });
}, long ? 5000 : 0);

would do what you want, and yield the event loop in the mean time. 

Node only does one thing at a time -- setTimeout is "call me when this time has passed; do what you need to until then"; while (stuff) is "do nothing but this until...."

Aria


Ryan Schmidt

unread,
Aug 21, 2014, 1:40:17 AM8/21/14
to nod...@googlegroups.com

On Aug 20, 2014, at 4:27 PM, Aria Stewart <ared...@nbtsc.org> wrote:
>
> The while loop is blocking -- it spins, doing useless work and NOTHING else until it expires. Your entire node process is hung during this time, so both requests "wait".
>
> Delaying with:
>
> setTimeout(function() {
> db.users.find({}, function(err, users) {
> callback(users);
> });
> }, long ? 5000 : 0);
>
> would do what you want, and yield the event loop in the mean time.
>
> Node only does one thing at a time -- setTimeout is "call me when this time has passed; do what you need to until then"; while (stuff) is "do nothing but this until...."

In addition, note that you are not handling errors. By convention, a callback should accept err as its first argument, which will be null if there is no error.

db.users.find({}, function(err, users) {
callback(err, users);
});

Or more simply:

db.users.find({}, callback);

Also by convention, a callback should be the last argument.


zladuric

unread,
Aug 21, 2014, 4:23:19 AM8/21/14
to nod...@googlegroups.com
So, a few problems:

- BLOCKING! 
That's your main issue. Your while() loop with dates is blocking your entire app server. It's not like say, PHP where each request gets its own instance of a PHP script. Here, one app, one script handles all requests. So when you block with while loop, you block all requests and that's why your second request is delayed. It's actually executed immediately, but immediately after it's been received. Which is after all those milions of while loop spins.

That's a very bad practice. You'll also probably notice that your CPU is at 100% during your long requests.

How to remedy it? Use setTimeout.

So, your userRepository would do something like this:

     getUsers: function(long, callback) {
    
         if(!long) {

             db.users.find({}, function(err, usersList) {
                  
                  callback(err, usersList);
             });
         } else {

             setTimeout(function() {

                 db.users.find({}, function(err, usersList) {
                  
                      callback(err, usersList);
                 });
             }, 5000);
        }
    }

Your second issue is method signature, like somebody has mentioned already. Your callbacks will usually be the LAST parameter to the request handlers. Also, in the callback, you will usually pass err or null as the first argument. Say that your db is down - you will want to pass that info and your render('index') might look like render('db-error'); instead.
Reply all
Reply to author
Forward
0 new messages