1

This is a javascript code from my node-webkit app, working with jquery and nedb for managing the databases.

librodb.find({_id: docs[i].libro}, function (err, bookdoc) {
window.titulo = bookdoc[0].titulo;
window.ISBN = bookdoc[0].ISBN;
});

That reads the entries from the db and returns them into an array (bookdoc).

for (var i = 0; i < docs.length; i++) {
  librodb.find({_id: docs[i].libro}, function (err, bookdoc) {
  window.titulo = bookdoc[0].titulo;
  window.ISBN = bookdoc[0].ISBN;
  });
  switch(docs[i].razon){
    case 1:
      $(".listed").append('<li><i class="fa fa-institution"></i><i class="fa fa-sign-in"></i>El '+docs[i].fecha+' '+docs[i].cantidad+' Libros ("'+window.titulo+'", ISBN: '+window.ISBN+') producidos.</li>');
      break;
      case 2:
        libreriadb.find({_id: docs[i].libreria}, function (err, librarydoc) {
          window.nombre = librarydoc[0].nombre;
        });
        $(".listed").append('<li><i class="fa fa-institution"></i><i class="fa fa-sign-in"></i>El '+docs[i].fecha+' '+docs[i].cantidad+' Libros ("'+window.titulo+'", ISBN: '+window.ISBN+') devueltos por Libreria ("'+window.nombre+'"), recibo '+docs[i].documento+'.</li>');
      break;
      case 3:
        $(".listed").append('<li><i class="fa fa-question"></i><i class="fa fa-sign-in"></i>El '+docs[i].fecha+' '+docs[i].cantidad+' Libros ("'+window.titulo+'", ISBN: '+window.ISBN+') en stock ingresaron por "'+docs[i].descripcion+'".</li>');
      break;
    }
}

The issue is that the variables window.titulo and window.ISBN are defined inside the reading database function, but outside there arent.

If i use

window.variablename=

When i call the variables after de librodb.find function both return "undefined".

if i use

var variablename=

or

variablename=

The execution stops with the following error: "ReferenceError: titulo is not defined" (in the place where i try to call it from the switch).

In all the three cases an alert inside the librodb.find function returns the value that is supossed to return.

How do i have to define or call the variables?

royhowie
  • 11,075
  • 14
  • 50
  • 67

2 Answers2

3

What you're running into is one of the many gotchas of asynchronous programming in javascript.

The meaning of the follow code is nuanced:

librodb.find({_id: docs[i].libro}, function (err, bookdoc) {
    window.titulo = bookdoc[0].titulo;
    window.ISBN = bookdoc[0].ISBN;
});

The librodb.find method is asynchronous, meaning it has background work it needs to do (maybe it needs to wait on disk access, or network traffic). Rather than halt all other code from running it starts doing things in the background and once that's finished it will call your code with the results.

So, what you are seeing is that when your code trying to use titulo and ISBN runs the callback has not yet been called, that is your code setting window.titulo and window.ISBN hasn't yet run!

Instead you need to delay running your code until the results of find come back. To do this move it inside the callback function. Of course this means the a simple for loop won't do what you want. Instead you can either write your own asynchronous loop using callbacks, or use a library such as async.js.

If you write it yourself it might look something like the following:

var i = 0;

function processNext() {
  librodb.find({_id: docs[i].libro}, function (err, bookdoc) {
    window.titulo = bookdoc[0].titulo;
    window.ISBN = bookdoc[0].ISBN;

    switch(docs[i].razon){
      case 1:
        $(".listed").append('<li><i class="fa fa-institution"></i><i class="fa fa-sign-in"></i>El '+docs[i].fecha+' '+docs[i].cantidad+' Libros ("'+window.titulo+'", ISBN: '+window.ISBN+') producidos.</li>');
        next();
      break;
      case 2:
        libreriadb.find({_id: docs[i].libreria}, function (err, librarydoc) {
          window.nombre = librarydoc[0].nombre;

          $(".listed").append('<li><i class="fa fa-institution"></i><i class="fa fa-sign-in"></i>El '+docs[i].fecha+' '+docs[i].cantidad+' Libros ("'+window.titulo+'", ISBN: '+window.ISBN+') devueltos por Libreria ("'+window.nombre+'"), recibo '+docs[i].documento+'.</li>');
          next();
        });
      break;
      case 3:
        $(".listed").append('<li><i class="fa fa-question"></i><i class="fa fa-sign-in"></i>El '+docs[i].fecha+' '+docs[i].cantidad+' Libros ("'+window.titulo+'", ISBN: '+window.ISBN+') en stock ingresaron por "'+docs[i].descripcion+'".</li>');
        next();
      break;
    }
  });

  function next() {
    i++;
    if(i < docs.length) {
      processNext();
    }
    else {
      // DONE
    }
}
processNext(); // Start the loop

Callbacks and asynchronous javascript definitely take some getting used to.

p.s. You really really shouldn't be putting the values on window. I haven't fixed that in your sample code, but now that it is inside the same function you should be able to get rid of the globals and simply make them normal variables.

Hargo
  • 1,256
  • 1
  • 16
  • 24
  • i have tried that before (without the next function), it didnt work (i dont remember why :B, i will try it again), but, why is the next function for? doesnt the for loop does that automatically?without that the loop repeats before the reading of the db is over? – Jiancerouno Taringa Dec 28 '14 at 01:45
  • @JiancerounoTaringa The `for` loop does largely the same thing, but does so synchronously; there is now way to pause the loop while waiting for the results of the find operations. Since we had to move the switch statement inside the callback if we used a for loop it would increment all the way to doc.length before the switch statement actually executed. Therefore `i` will equal `doc.length` which isn't at all what you want. Also the results of find could come back out of order and your inserted elements could end up in the wrong order. – Hargo Dec 28 '14 at 01:50
  • oh, i just remembered, inside the librodb.find the bookdoc works but the docs (from a parent find function) doesnt, and inside the libreriadb.find the librarydoc works but both the docs and bookdoc doesnt – Jiancerouno Taringa Dec 28 '14 at 01:52
  • Make sure you don't have any other code that changes those variables. The code inside the callbacks share the same variables but will run at some largely unknown time in the future, so if you have other code that changes them you'll have a problem. – Hargo Dec 28 '14 at 01:56
  • i havent, well, it doesnt matters, if it doesnt work i will define the values i need as global variables before entering the next finding function. thank you ^^ – Jiancerouno Taringa Dec 28 '14 at 02:02
1

you just need to reorganize your code so that the dependencies on the callback are utilized inside the callback. For example:

for (var i = 0; i < docs.length; i++) {
  librodb.find({
    _id: docs[i].libro
  }, function(err, bookdoc) {
    var titulo = bookdoc[0].titulo;
    var ISBN = bookdoc[0].ISBN;
    switch (docs[i].razon) {
      case 1:
        $(".listed").append('<li><i class="fa fa-institution"></i><i class="fa fa-sign-in"></i>El ' + docs[i].fecha + ' ' + docs[i].cantidad + ' Libros ("' + titulo + '", ISBN: ' + ISBN + ') producidos.</li>');
        break;
      case 2:
        libreriadb.find({
          _id: docs[i].libreria
        }, function(err, librarydoc) {
          var nombre = librarydoc[0].nombre;
          $(".listed").append('<li><i class="fa fa-institution"></i><i class="fa fa-sign-in"></i>El ' + docs[i].fecha + ' ' + docs[i].cantidad + ' Libros ("' + titulo + '", ISBN: ' + ISBN + ') devueltos por Libreria ("' + nombre + '"), recibo ' + docs[i].documento + '.</li>');

        });
        break;
      case 3:
        $(".listed").append('<li><i class="fa fa-question"></i><i class="fa fa-sign-in"></i>El ' + docs[i].fecha + ' ' + docs[i].cantidad + ' Libros ("' + titulo + '", ISBN: ' + ISBN + ') en stock ingresaron por "' + docs[i].descripcion + '".</li>');
        break;
    }

  });

}

The big picture is that your callback function will do all the processing that depends on its result.

As a rule of thumb, you should not clutter the window object, so you almost never want to define any variables there, certainly not for a simple case like this.

Born2Code
  • 1,055
  • 6
  • 13