6 ms·
Not to focus too myopically on the given example, but I can’t help but wonder why it’s a requirement that the first file be handled specially? A less contrived
by mbostock 14y ago
Not to focus too myopically on the given example, but I can’t help but wonder why it’s a requirement that the first file be handled specially? A less contrived example would make the argument more convincing.
If I wanted to compute the size of one file relative to a set, I’d probably do something like this:
queue()
.defer(fs.stat, "file1.txt")
.defer(fs.stat, "file2.txt")
.defer(fs.stat, "file3.txt")
.awaitAll(function(error, stats) {
if (error) throw error;
console.log(stats[0].size / stats.reduce(function(p, v) { return p + v.size; }, 0));
});
Or, if you prefer a list:
var q = queue();
files.forEach(function(f) { q.defer(fs.stat, f); });
q.awaitAll(…); // as before
This uses my (shameless plug) queue-async module, 419 bytes minified and gzipped: https://github.com/mbostock/queue https://github.com/mbostock/queue
A related question is whether you actually want to parallelize access to the file system. Stat'ing might be okay, but reading files in parallel would presumably be slower since you'd be jumping around on disk. (Although, with SSDs, YMMV.) A nice aspect of queue-async is that you can specify the parallelism in the queue constructor, so if you only want one task at a time, it’s as simple as queue(1) rather than queue(). This is not a data dependency, but an optimization based on the characteristics of the underlying system.
Anyway, I actually like promises in theory. I just feel like they might be a bit heavy-weight and a lot of API surface area to solve this particular problem. (For that matter, I created queue-async because I wanted something even more minimal than Caolan’s async, and to avoid code transpilation as with Tame.) Callbacks are surely the minimalist solution for serialized asynchronous tasks, and for managing parallelization, I like being able to exercise my preference.
- DougBTX 14y agoIt looks like you're able to return one of those queues from a function and allow some other code to call .await(). Being able to return something is a useful feature of promises too, seems like there might be more overlap there.
- jasondenizac 14y ago> I just feel like they might be a bit heavy-weight and a lot of API surface area to solve this particular problem. The surface area is `.then()`
- hyperpape 14y agoLack of standardization makes people go crazy. If another programming language had an API this simple, people would never think it is heavyweight. But because you're always rolling your own, people get obsessed with the smallest things in js-land.
- Derander 14y ago>> but reading files in parallel would presumably be slower since you'd be jumping around on disk There is a lot of engineering that goes into making parallel reads go fast. Some combination of the file system and disk controller will probably be smart enough to recognize the opportunity for sequential reads and execute them as such if possible. This is not always true, and it does not undermine the rest of what you have written. I just think it's interesting to keep in mind that operating systems implement a lot of helpful machinery that user-level programmers forgot about.
- justincormack 14y agoStat just reads metadata that is (hopefully) cached in memory. Linux does not have any async APIs for reading metadata anyway. Examples are always a bit contrived, it doesn't matter.
- tel 14y agodo f1 <- fsStat "file1.txt" f2 <- fsStat "file2.txt" f3 <- fsStat "file3.txt" let ratio = (size f1) / (sum $ map size [f1, f2, f3]) print ratio Or, if you prefer a list do fs <- mapM fsStat files let ratio = (size . head $ fs) / (sum . map size $ fs) print ratio And that seems to be one small example of why you may have already invented monads. I've been loving the impact of Javascript—modify and immediately see it on the browser—but every time I'm not using Haskell I miss it dearly.
- dchichkov 14y agoPlease use more readable names in your code. Use of names like 'fs' in key places makes it unreadable.
- schrototo 14y agoGenerally this is of course good advice, but in Haskell it is common practice to use very short names (e.g. x, x', xs, ...) if the context is clear (which it usually is due to small scope, clear function names, type signature, etc.). This makes code much more concise and readable (it also makes it look very "mathematical").
- skrebbel 14y ago> (it also makes it look very "mathematical") Has that ever been an advantage?
- psionski 14y agoFor people that like math, sure, why not. When implementing mathematical concepts, if you squint at Haskell code you can see the original formulas, which should make it easier for people used to this way of thinking. EDIT: I'm not implying it's useful just for programming "math stuff", after all, everything can be reduced to a mathematical problem - including game engines[1], web application frameworks[2], etc. [1] http://www.cse.unsw.edu.au/~pls/thesis/munc-thesis.pdf http://www.cse.unsw.edu.au/~pls/thesis/munc-thesis.pdf [2] https://github.com/yesodweb/yesod https://github.com/yesodweb/yesod
- account_taken 14y agoThese kinds problems can easily be solved with promises too. It would be even simpler if `fs.stat` returned a promise and there are promise libraries that do that. Promises is a small library I use, probably about the same number of bytes as your library, as I transition my code from callbacks to promises. var queue = new Promises; fs.stat("file1.txt", queue.cb()); fs.stat("file2.txt", queue.cb()); fs.stat("file2.txt", queue.cb()); queue.all() .then() .fail(); But, comparing how promises solves the same flow as callbacks misses the point. Here's an example where an action is taken when two events fire (promises shine here): pub.on('foo', function() { promise1.fulfill(); }); pub.on('bar', function() { promise2.fullfill(); }); Vow.all([promise1, promise2]).then(...).fail(...);