Reputation: 33
I'm attempting to implement a series function for flow control purposes but getting unexpected results. I need to check for the existence of a file, as the result a shell cmd initiated by Node, over the course of say 5000 ms. Using Mixu's Node book I am utilizing a series function for sequential execution (section 7.2.1). Per the Node book, this pattern would be appropriate if you needed one result to feed into another, but that does not seem to be the case as the final()
is being executed before any of the callbacks from the argument functions return.
Below should be all the code needed to reproduce what I am seeing.
Expected output: (assume the file being checked for was created in < 1000ms)
1347312844082
true
1347312845082
true
1347312846083
true
1347312847082
true
1347312848082
true
true <-- last true result of console.log() in last param function of series function.
Actual output:
1347312844082
1347312845082
1347312846083
1347312847082
1347312848082
false <-- last param function of series function being evaluated before series args callbacks.
true
true
true
true
true
I'm guessing the callbacks from my sleep functions (which contain the fs.check
calls)
args
param of the series function all being invoked by slice.call(arguments)But this seems contradictory as to what is the described behavior in the Node book.
Note: I'm trying to avoid regressing back into sync calls (fs.existsSync
) in an attempt to break my brain and maybe finally "get it" when it comes to Node/async programming.
var outputFile = 'C:\\Some\\Valid\\Path\\foo.txt';
var successCheck = false;
series([
sleep(1000, function () {
printTime();
fs.exists(outputFile, function (exists) {
console.log(exists);
successCheck = exists;
});
}),
/* middle three deleted for brevity */
sleep(1000, function () {
printTime();
fs.exists(outputFile, function (exists) {
console.log(exists);
successCheck = exists;
});
}),
sleep(1000, function () {
printTime();
fs.exists(outputFile, function (exists) {
console.log(exists);
successCheck = exists;
});
})
], function() {
console.log(successCheck);
});
function sleep (msInterval, callback) {
var now = new Date().getTime();
while(new Date().getTime() < now + msInterval) {
// do nothing
}
callback();
}
function series (callbacks, last) {
var results = [];
function next() {
var callback = callbacks.shift();
if(callback) {
callback(function() {
results.push(Array.prototype.slice.call(arguments));
next();
});
} else {
last(results);
}
}
next();
}
function printTime () { var now = new Date().getTime(); console.log(now); }
Upvotes: 3
Views: 255
Reputation: 144832
I see several problems with your code. First and foremost, your approach to "sleep" is completely broken.
JavaScript (and thus Node) is a single-threaded, event-driven language. If you do anything that blocks, you block your entire program.
Your sleep
function blocks the entire program by spinning until msInterval
milliseconds pass.
series
. In order to pass it arguments, we must evaluate those arguments.sleep
is invoked on the next line.sleep
spins for 1000 ms.sleep
invokes its callback
.sleep
returns undefined.sleep
is invoked; the last 5 steps are repeated....after 5 seconds, all calls to sleep
have completed. You now have an Array that looks like this:
[undefined, undefined, undefined, undefined, undefined]
since sleep
doesn't return anything.
series
can be invoked with the arguments an array of undefineds and a Function.results
is instantiated.next
is invoked.else
branch.last
with an empty array. (We never put anything in results
)last
callback prints successCheck
, which is false
, and returns.fs.exists
callbacks are invoked in the order they completed (which is probably, but not necessarily the order they were issued).successCheck
(which is never actually read again).
Never, ever, ever spin in a while
loop. You could rewrite sleep
like this:
function sleep(ms, cb) {
return function(done) {
setTimeout(function() {
cb(done);
}, ms);
}
}
Here, we return a function that will be invoked later. This way, the array that gets passed to series
is filled with the inner sleep
function. When you call this sleep
, nothing has actually happened yet.
When series
calls the first array element, it is actually invoking the returned anonymous function, where we've closed over the ms
and cb
that were originally passed to the sleep
call. The function sets a timeout for ms
milliseconds. What that expires, the innermost anonymous function is invoked, which calls cb
. It passes the done
function as its argument, which is actually the anonymous function from series
' next
that pushes onto results
and calls next
.
Because the next step of your series isn't started until the previous step indicates it's done, you have to call the done
callback from the step's function:
sleep(1000, function (done) {
printTime();
fs.exists(outputFile, function (exists) {
console.log(exists);
successCheck = exists;
done();
});
});
Of course, the way you really want to solve the problem ("How do I do something after this file has been created?") is not by polling fs.exists
.
Instead, use filesystem change notifications! fs.watch
allows you to listen for changes to files or folders.
var watcher = fs.watch('C:\\Some\\Valid\\Path\\', function(e, filename) {
if (filename == 'foo.txt') {
// success!
watcher.close(); // stop watching for changes
}
});
Upvotes: 2