1

I use cheerio module on node.js. And I've known that a variable declared in a function without 'var' keyword is global scope.

Today I recognized when cheerio loads http body into $ variable, the variable doesn't use the keyward 'var'. I'm not having any problem with it. But suddenly I got confused. In my opinion the variable $ is global scope so all session connects to server must share the same variable.

I think I have wrong concept with javascript. please let me fix this problem.

var cheerio = require('cheerio');

request(url, function(err, resp, body){
  if (err) throw err;
  $ = cheerio.load(body);
});
positoy
  • 39
  • 3
  • It may or may not cause bugs, depending on the rest of your code. For example, you didn't show us how exactly these requests are created. But in general, yes, you'd better use `$` local to each request handler call. – raina77ow Aug 11 '14 at 11:59
  • This http://stackoverflow.com/questions/111102/how-do-javascript-closures-work and this http://stackoverflow.com/questions/4862193/javascript-global-variables seems to be relevant – xmojmr Aug 12 '14 at 17:16

1 Answers1

2

In jQuery, the context is implicit, it's the one of window.document.

When you use cheerio, you can have several contexts, but one per instance, and you bind this context using the load function. There's no one global $, but several ones if needed :

var $1 = cheerio.load(body);
var $2 = cheerio.load(someotherbody);

Your own code would have been less confusing if you hadn't forgotten the var keyword. And yes you have a bug : if you're handling several requests in "parallel" they'll all have the same context (in fact it depends, you may have something worse than a bug : a potential bug that will unexpectedly arise later when adding some correct code).

Here's a typical code which probably looks like your own :

request(url, function(err, resp, body){
  if (err) throw err;
  $ = cheerio.load(body);
  doSomethingAsynchronous(function callback(){
      answer($, resp);
  });
});

Here, the bug arise when doSomethingAsynchronous doesn't complete before another request starts to be handled. In your case, there are a few reasons why you don't see any bugs :

  • you don't use $ after you made an asynchronous action and as there's only one user thread in node there can be no collision
  • doSomethingAsynchronous is fast enough and thus is executed before another request starts to be answered
  • everything happens so fast (remember : node is fast) that you never really have more than one request being handled
  • you explicitly pass $ as argument to doSomethingAsynchronous, you don't refer to the global variable in the callback

But the bug will arise as soon as you enter the realm of "real applications" where you have many parallel requests and asynchronous actions.

Fix your code :

var cheerio = require('cheerio');

request(url, function(err, resp, body){
  if (err) throw err;
  var $ = cheerio.load(body);
  // use $ here, you have one different instance per request, you may
  // pass it, either explicitly as argument or using the closure
});
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • yes, but the contexts are in global scope, like global.$1, global.$2 aren't they? When next client connects to server, server still has the same global variables. I thought there could be multiple session in server when server is very busy. how come there's no collision? – positoy Aug 11 '14 at 12:01
  • If you use the `var` keyword, you create a variable whose scope is the current one (in your case the one of the `request` call, not the global one). Here's a [working example](https://github.com/Canop/miaou/blob/master/plugins/wikipedia/plugin.js#L31). – Denys Séguret Aug 11 '14 at 12:01
  • The main reason you see no problem today is that requests are handled so fast you don't have two of them in parallel (having requests handled in "parallel" in node would also mean you have some kind of asynchronous action done for the handling of the request). – Denys Séguret Aug 11 '14 at 12:05