0

I've stubled upon a function (here on SO) which writes a file, but makes sure to not overwrite a file:

function writeFile(i){
    var i = i || 0;
    var fileName = 'a_' + i + '.jpg';
    fs.exists(fileName, function (exists) {
        if(exists){
            writeFile(++i);
        } else {
            fs.writeFile(fileName);
        }
    });
}

Now there is an interesting comment below which says:

Minor tweak: Since JavaScript doesn't optimize tail recursion away, change writefile(++i) to process.nextTick(function(i) {writefile(++i);}); that will keep your stack from blowing up if you have to go through lots of file names.

Please explain. How does process.nextTick prevent the stack from blowing up?


Update: Turns out the assumption in the comment was wrong! Anyway, cases do exist where process.nextTick plays a role in preventing a stack overflow (see examples in accepted answer).

Community
  • 1
  • 1
borisdiakur
  • 10,387
  • 7
  • 68
  • 100
  • That's rubbish. Your stack would not blow, as your function [runs asynchronously](http://stackoverflow.com/a/17090506/1048572). You're not using `fs.existsSync`! – Bergi Feb 05 '15 at 20:23
  • @Bergi Yes, that's what loganfsmyth pointed out already (see comments below accepted answer). – borisdiakur Feb 05 '15 at 20:32

1 Answers1

1

If you had a function that called itself synchronously, say, 200k times, the code will exit with error due to the stack being too deeply nested. process.nextTick avoids that similar to the way setTimeout(fn,0) will by clearing the callstack on each iteration. It simply defers the execution of the passed function until the next time the eventloop runs.

More reading (but out of date): http://howtonode.org/understanding-process-next-tick

Example function that would fall into this trap:

(function doWork (i) {
    if (i === 0) return;
    console.log('Doing work!');
    doWork(--i);
}(2000000))

and the same function fixed with process.nextTick:

(function doWork (i) {
  if (i === 0) return;
  console.log('Doing work!');
  process.nextTick(function () {
    doWork(--i);
  });
}(2000000))

However, in your case this won't be a problem because the code is asynchronous due to fs.exists(, therefore the comment you were referring to is incorrect.

Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • 3
    In this particular case, `fs.exists` is already async though, so the call stack is already reset. – loganfsmyth Feb 05 '15 at 18:33
  • 1
    Ah, good point. That would make the comment in the original question wrong too. Help me get it deleted by flagging as not constructive or other. – Kevin B Feb 05 '15 at 18:34
  • Please correct me: Every time we enter a new iteration in the event loop the callstack is zero (otherwise we cannot enter the next iteration). Every asynchronous function defers its callback execution to the next iteration. So the callback resides in memory and is waiting for an event. If that event is triggered, the callback execution is put on the current stack. Otherwise the callback (and its scope) is passed to the next itaration in memory. Is that right? – borisdiakur Feb 05 '15 at 21:02
  • mostly, yes, except pending on what you meant by waiting for an event. the callback queue is for things that are ready to be executed, not things that are waiting for an event to happen. When the event loop runs, it'll pull the next callback from the queue and run it. When the stack is clear again, the event loop will run again, pulling another callback off the stack. – Kevin B Feb 05 '15 at 21:05
  • What if I have a pending request? There must be some kind of events for I/O operations. If there is no such an event triggering execution of the callback respectively putting it on the stack (the latter formulation is actually the correct one), how else would it work? I just read [here](http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/) that _An event loop is "an entity that handles and processes external events and converts them into callback invocations"._ I guess I got it figured out now. Thanks a lot! – borisdiakur Feb 05 '15 at 21:37
  • That's outside of the javascript engine, not handled by javascript. Compare it to DOM events in the browser. A click event is something that is handled by the DOM Api, not javascript. When a click happens on an element, the DOM Api looks for any bound event handlers. If one is found, the callback is inserted into the callback queue. – Kevin B Feb 05 '15 at 21:38
  • Yes, that's what I ment when I was speaking of events. Sorry for the confusion. – borisdiakur Feb 05 '15 at 21:39