user2195738
user2195738

Reputation: 61

"Yield" a computation in Node.js

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?

Upvotes: 2

Views: 1958

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074335

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
})();

Upvotes: 7

Related Questions