1

I'm a novice to Javascript, and experienced programmer, and i'm struggling real hard with grasping the concept in pattern and design of the best structure to use and approach while building applications. A few areas are quite new to me, one being mastering the scopes and closure of Javascript.

So to make this as clean as possible for you guys i've attached a sample of code to this post, hoping that someone may put my logic to place and give me a guideline to re-think and think right.

The code that i'm posting is part of a very simple file system / helper library (Node.js, express) that is used to scan directories, run a few conditions on those directories and return the result as a map in dimensions of an array to another library (not posted). The code you see is my current "testing" condition. I've commented it fairly.

The gist: https://gist.github.com/jimmiehansson/6235613 (FULL VIEW OR CODE / LIBRARY)

Sample code block (with problem in returning values)

ApiFS.prototype.scan = function(){          
    var self = this;
    var b;
    fs.readdir(stgs.fsPath, function(err,folders){      
        if(folders.length<1) { console.log(errLog.fsEmpty); return; }       
        rslt = self.walk(folders);          
        rslt.forEach(function(itm){
            if(!self.exclusion(rslt)){ b=self.loader(stgs.fsPath,itm); }        
        });                     
    }, ioResponse);
    console.log(b); // undefined <----      
}

So enough icebreaking. My biggest problem:

No matter what I do, the scope (hoisting) and closures which are fairly small, get in my way. I use no getter or setter currently, because I simply don't know how I should? Is my logic completely off, or am I missing something rather fundamental?

In the code you will see that the scan() prototype function makes a single call to each function "helper" that return either true/false value or an array.

I can work with these simply by returning values to the main scan() function, however I cannot for the sake of my life get those values from the fs.readdir() function to return outside of the scope, for instance outside of ioResponse callback.

What is my logic missing, how can I better approach this (even if it means reconstructing every thing) I am worried that I am doing something terribly wrong in my approach to design and layout these functions, and how i handle the values in between them.

Any assistance is greatly appreciated!

2 Answers2

2

This has nothing to do with scope, it is about timing. readdir is asynchronous. It sends off the request to the filesystem and then lets the previous function carry on.

console.log(b); executes before b=self.loader() does.

If you want to work with data from a function that takes a callback, you need to perform that work inside the callback.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • I'm asking if you could elaborate this a bit, not because I consider your answer wrong, simply to assure that I get a clear understanding. My structure (design) and logic is correct as it is, I simply am approaching asynchronous function handling of values wrong? So instead of executing the forEach() and my custom functions "inside" the asynchronous wheel, I should place these in the ioResponse? Am I misinterpreting your answer? Thanks – Jimmie Hansson Aug 14 '13 at 21:27
0

Quentin is correct. You'll definitely have to get used to the async nature of node programs. Just to make it clear, this should work as you expect.

ApiFS.prototype.scan = function(callback){          
    var self = this;
    var b;
    fs.readdir(stgs.fsPath, function(err, folders) {      
        if(folders.length<1) { console.log(errLog.fsEmpty); return; }

        var results = [];
        async.each(folders, function(folder) {
            if(!self.exclusion(folder)) { 
                results.push(self.loader(stgs.fsPath, itm)); 
            }            
        }, function() {});

        if(typeof(callback) == "function") {
            callback(b);
        }
    });
}

ApiFS.scan(function(files) {
    // Do something with files
});

Untested Refactoring of your Gist

var fs = require('fs'),
    async = require('async'),
    _ = require('underscore'),
    path = require('path');

ApiFS = function(options){
    errLog = [];
    errLog.fsEmpty = "No existing directories or empty, cannot continue."; 
}

ApiFS.prototype.scan = function(callback) {          
    var self = this,
        results = {};
    fs.readdir(options.fsPath, function(err, folders) {
        if(folders.length<1) return callback(errLog.fsEmpty);       
        async.forEach(folders, function(folder) {
            if(!self.exclude(folder)) {
                self.loadFiles(folder, function(err, files) {
                    results[folder] = files;
                });
        }, function(err) {
            callback(err, results);
        });                     
    });     
}

ApiFS.prototype.exclude = function(folder){       
    if(options.exclude.length === 0){
        return false;
    }

    return _.contains(options.exclude, folder);
}

ApiFS.prototype.loadFiles = function(folder, callback) { 
    fs.readdir(path.join(options.fsPath, folder), function(err, files) {
        callback(err, files);
    });
};

exports = module.exports = ApiFS;
Timothy Strimple
  • 22,920
  • 6
  • 69
  • 76
  • Timothy, thank you for explaining this to me a bit further. I understand how you mean now seeing the code "example". I am wondering, if I was to return the value of b from self.loader(); (An array) as a final return from that function "scan()" I would approach this using the callback from forEach() ? I am a bit confused as to how I would make a final return, after fs.readdir() of the values collected from "inside" the fs.readdir() - Not to seem short or stupid. Being my third day with node development. eg. function scan(){ var b; fs.readdir(){ iterate... b=values } return b; } – Jimmie Hansson Aug 14 '13 at 21:43
  • Keep in mind, since you're using async functions inside of scan, you won't be able to return a value, you'll have to provide a callback that is called once scan is done. I'll update the example to show you the callback and how to use it. – Timothy Strimple Aug 14 '13 at 21:47
  • Thank you. That would be fantastic, I'm learning this better visually "in-code". – Jimmie Hansson Aug 14 '13 at 21:49
  • Okay, updated. Another thing to keep in mind is the only non-blocking code in node is the code you write. I'm not sure what self.loader does, but if it's at all slow, or the forEach has a LOT of items, the entire application will hang until it's done being processed. You might want to use an async version of forEach like each from the async module. https://github.com/caolan/async#each – Timothy Strimple Aug 14 '13 at 21:52
  • Thanks for the update, I'm getting a better understanding of this now, I'll simply have to get use to the way node likes to handle returns and calls between functions. This seem more logical to me now. The forEach presented above use to be asynchronous using async.each/map as I am doing in "walk()" see gist: https://gist.github.com/jimmiehansson/6235613 – Jimmie Hansson Aug 14 '13 at 21:57
  • Being totally stumped over not approaching this correctly, I changed the most of it into blocking code. Thank you though, I will take your example into practice as a guideline in my thinking. – Jimmie Hansson Aug 14 '13 at 21:58
  • You need to fix your walk function as well. It will return an empty array before doing any of the async.each calls. However it doesn't look like it's doing trying to do anything except converting an array of folders into the same array of folders! – Timothy Strimple Aug 14 '13 at 22:01
  • In walk() i've attempted to add folder items / directories into an array from the async.each() function, so that I can return that array to the scan() function, i'm clearlyin need to get my logic into place with this, could you provide me an example where the value of walk() - the array, is returned to the scan() function and output as a final return for scan() ? Greatly appreciated – Jimmie Hansson Aug 14 '13 at 22:09
  • Okay, I removed the functions that weren't really doing anything and made some guesses about your desired functionality, but I've refactored your Gist and updated the answer. What scan now does is run against a folder, get a list of all subfolders, and then a list of files in each subfolder. – Timothy Strimple Aug 14 '13 at 22:34
  • Timothy, this has given me an understanding, clearing this out and helping me. Its been quite a headache trying to grasp this into my old thinking. Its much easier to understand now, thank you for spending a little extra time providing the full example of the refactor its exactly what I needed. – Jimmie Hansson Aug 14 '13 at 22:38