2

I have a somewhat bigger computation (~0.5sec) in my node app, and I want to make it non-blocking without using a webworker. (I think a webworker would be a little overkill in this situation)
Is there a way to force a return to the main loop, in order to give node the chance to process another request?

1 Answers1

6

It sounds like you're saying you want to do the calculation in bite-sized chunks, on the main thread, rather than spinning it off to its own thread (e.g., using webworker-threads or child processes or the like).

I can think of three options:

  1. ES6 Generator functions.

  2. A function that returns its state and lets you call it again with a state object (which is basically what ES6 generator functions are, but they give you much nicer syntax).

  3. A function that continues running on its own by using nextTick, without your being in control of how it runs.

The third option offers the calling code the least control; the first and third are probably simplest to implement.

ES6 Generator Function

You can use ES6's generator functions in recent versions of NodeJS via the --harmony_generators flag. Generator functions can "yield" back to the calling code, which can then tell them to pick up where they left off later.

Here's an example of a simple generator that counts to a limit, adding one to the count each time you call it:

function* counter(start, inc, max) {
    while (start < max) {
        yield start;
        start += inc;
    }
}

var x = counter(1, 1, 10);
console.log(x.next().value); // 1
console.log(x.next().value); // 2
console.log(x.next().value); // 3

Note that that does not spin the calculation off to a different thread, it just lets you do a bit of it, do something else, then come back and do a bit more of it.

A function that returns and accepts its state

If you can't use generators, you can implement the same sort of thing, making all your local variables properties of an object, having the function return that object, and then having it accept it again as an argument:

function counter(start, inc, max) {
    var state;
    if (typeof start === "object") {
        state = start;
        if (!state.hasOwnProperty("value")) {
            state.value = state.start;
        } else if (state.value < state.max) {
            state.value += state.inc;
        } else {
            state.done = true;
        }
    } else {
        state = {
            start: start,
            inc: inc,
            max: max,
            done: false
        };
    }
    return state;
}

var x = counter(1, 1, 10);
console.log(counter(x).value); // 1
console.log(counter(x).value); // 2
console.log(counter(x).value); // 3

You can see how generators simplify things a bit.

A function that runs without the calling code controlling it

Here's an example of a function that uses nextTick to perform its task in bite-sized pieces, notifying you when it's done:

function counter(start, inc, max, callback) {
    go();

    function go() {
        var done = start >= max;
        callback(start, done);
        if (!done) {
            ++start;
            process.nextTick(go);
        }
    }
}

counter(1, 1, 10, function(value, done) {
    console.log(value);
});

Right now, you can't use generators at global scope (you probably don't want to anyway), you have to do it within a function in strict mode. (Even a global "use strict" won't do it.) This is because V8 is still getting its ES6 features...

Complete sample script for a current version of node (allowing for the above):

"use strict";
(function() {
    function* counter(start, inc, max) {
        while (start < max) {
            yield start;
            start += inc;
        }
    }

    var x = counter(1, 1, 10);
    console.log(x.next().value); // 1
    console.log(x.next().value); // 2
    console.log(x.next().value); // 3
})();
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I don't think he meant "`yield`" literally, rather something like `setTimeout`/`nextTick` that allows the main thread to process another request before continuing. – Bergi May 26 '15 at 10:07
  • @Bergi: Right. To use `nextTick` and similar, though, you need a function that can stop what it's doing while retaining its state, and pick it up again when you call it back -- because you need a callback to pass to `nextTick`. – T.J. Crowder May 26 '15 at 10:13
  • What about completely demand the computation to another process? http://stackoverflow.com/questions/3491811/node-js-and-cpu-intensive-requests – SharpEdge May 26 '15 at 10:24
  • I'd say if the computation does not do any I/O, it must either be implemented as a generator (or something that "emulates" similar behaviour) that can be run into completion incrementally or it must be put into a separate process. JS can only run a single statement at any given time, and running a long function will effectively block the rest of the program. – Robert Rossmann May 26 '15 at 10:52