Passing values between functions?

18 views
Skip to first unread message

bsodzik

unread,
Jan 20, 2014, 10:41:09 AM1/20/14
to defer...@googlegroups.com
Hello medikoo,

I am wondering what is the best strategy for passing arguments from one asynchronous function to another. I hope the example will clarify my scenario:

Let assume you need to go through all files in a directory and display size of each file.
Without deferred I would do it like that:

fs.readdir(root, function (err, files) {
    files.map(function (file) {
        return path.join(root, file);
    }).forEach(function (file) {
        fs.readFile(file, function (err, data) {
            if (err) {
                console.error(err);
                return;
            }
            console.log('File "%s" size is %d', file, data.length);
        });
    });
});


Now I would like to achieve the same results with deferred. My code looks like that (node fs functions are promisified):

readdir(root).map(function (file) {
    return path.join(root, file);
}).map(function (file) {
    return deferred(file, readFile(file));
}).map(function (data) {
    console.log('File "%s" size is %d', data[0], data[1].length);
}).done(function () {
    console.log("Processing finished!");
}, function (err) {
    console.error(err);
});

The problematic line is:
return deferred(file, readFile(file));

It allows me to pass file argument for further processing, but unfortunately in next function I receive an array and need to access it with indexes:
console.log('File "%s" size is %d', data[0], data[1].length);

It looks like a workaround rather than professional way of handling the situation, therefore I would like to ask you if you see any better ways of solving my example? I am sure there are some :)

Kind regards,
Tomasz

Mariusz Nowak

unread,
Jan 20, 2014, 11:14:05 AM1/20/14
to defer...@googlegroups.com
Hello Tomasz!,

In such cases I usually prepare some dict object and pass that with needed data after processing of item is finished. I never used `deferred` function for that, it's rather weird approach.
On my side I would wrote it that way:

readdir(root).map(function (file) {
    var filePath = path.join(root, file);
    return readFile(path)(function (content) {
        return { path: filePath, content: content };
    });
}).done(function (data) {
    data.forEach(function (data) {
        console.log('File "%s" size is %d', data.path, data[1].content.length);
    });
    console.log("Processing finished");
}); // if it's simple script and no handling is prescribed then just let eventual error throw


Also, as you see I prefered to do final processing in callback passed to `done`, and not within another `map` transformation (which would produce another array value I don't consume). It's a bit cleaner.

Other note: instead of reading whole file, you can use much lighter fs.lstat to get size of file, but I assume you just simplified your use case and in real code you need to process file content.

bsodzik

unread,
Jan 20, 2014, 5:53:38 PM1/20/14
to defer...@googlegroups.com
Thank you Mariusz for detailed answer. Indeed it looks like the only reasonable option is to invoke map() only once and in that invocation perform most of the code.

Of course my example is just an explanatory one. I am aware that in my code there is one redundant map invocation (because there is no forEach on promise object ;]).

What I want to achieve is to present that code in the "flattest" possible way. I want to show how we can handle "callback hell" with deferred by chaining particular operations. Because of that I was doing everything to limit indentation in code to one. I know it's a little bit crazy, but hey - at least I can experiment a little bit more with your framework :)

Maybe you know of some better example that could be used to present significant simplification of code with deferred (for newbies)? :)

Mariusz Nowak

unread,
Jan 21, 2014, 2:24:27 AM1/21/14
to defer...@googlegroups.com


On Monday, January 20, 2014 11:53:38 PM UTC+1, bsodzik wrote:
Of course my example is just an explanatory one. I am aware that in my code there is one redundant map invocation (because there is no forEach on promise object ;]).

Actually you can call any method on resolved promise value, also forEach:

readdir(root).map(function (file) { /* ... */ }).invoke('forEach', function (data) {

    console.log("File "%s" size is %d", data.path, data[1].content.length);
}).done(function () {

    console.log("Processing finished");
});

but still, it's not that clean, as again `invoke` produces transformation. It runs forEach in try/catch clause, and creates another promise of which resolved value is forEach result. I prefer to not invoke such obsolete transformations.
 

What I want to achieve is to present that code in the "flattest" possible way. I want to show how we can handle "callback hell" with deferred by chaining particular operations. Because of that I was doing everything to limit indentation in code to one. I know it's a little bit crazy, but hey - at least I can experiment a little bit more with your framework :)

Thing with promises is that they gain advantage only when your flow gets more complex. In simple cases, plain callbacks are much better and lighter, and it's hard to see then how promises can be helpful.
Super-power of promises lie in its composition capabilities, you can very neatly compose very complex asynchronous flows, of which composition with plain callbacks would cause many headaches


Maybe you know of some better example that could be used to present significant simplification of code with deferred (for newbies)? :)


Best (simplest) example I know is e.g. concatenation of all .js files in directory, it opens deferred documentation: https://github.com/medikoo/deferred#comparision-with-callback-style :)

 

bsodzik

unread,
Jan 21, 2014, 4:55:33 AM1/21/14
to defer...@googlegroups.com
Yeah, probably I should go with official examples rather than trying to invent some new ones. At least thanks to this conversation I learned the recommended way for passing "composite" objects through the flow and that I should do final processing in done() method. Thank you! :)
Reply all
Reply to author
Forward
0 new messages