welcome: please sign in

No pages like "Coroutines"!

Clear message
location: Coroutines

This article is in process.

The most cogent plain-language definition of coroutine that I have encountered is this: a coroutine is just a function with multiple entry and exit points.

The first prerequisite to understanding how Conkeror's coroutine library works is to read and grok everything about generators in the MDC article, New in JavaScript 1.7. Generators are the JavaScript primitive that makes it possible to suspend execution, and is thus the basic requirement for implementing coroutines in JavaScript.

Next, read MinibufferReadWalkthrough. We'll be referring to it as an example throughout this article.

JavaScript has the same kind of generators as Python, and in the Python world, they are called enhanced generators. (Because prior to Python 2.5, Python had a more limited implementation.)

With a coroutine, not only can the original called function yield, but any function it calls in turn (and any function to any depth in the call chain) can also yield.

With generators, only the originally called function can yield.

Emulating coroutines on top of generators is the point of Conkeror's coroutine library. The technique is called trampolining.

In a trampoline-coroutine system, a coroutine-thread is a stack of generators.

Recall from MinibufferReadWalkthrough the pair (yield CONTINUATION) and yield SUSPEND. Before suspending, the coroutine needed to get a reference to its own continuation, to stash somehwere where a later event could use it to resume the suspended thread. The special loop is running inside the coroutine thread, orchestrating the proper stacking of the generators. In effect, the special loop needed to have on hand a reference to its own continuation, in order to send back to any generator that performed (yield CONTINUATION). But generators are a far cry from call/cc. You can't call a generator with its own continuation as a parameter. However, what you can do is this: after starting the generator, have an extra yield, so that you can send the contination into it.

Compare the generators of JavaScript and Python with the coroutines of Lua. With generators, function arguments are bound during the creation of the generator and the first send (which starts the generator) cannot send any value except undefined.

function my_generator (a, b, c) {
    var cc = yield;
    while (true) {
        // yield values here
    }
}

var cc = my_generator(1, 2, 3); // parameters bound at instantiation of the generator.
cc.send(undefined); // start the generator. (no args allowed)
cc.send(cc); // it's almost like call/cc!

Examples

Periodic Fetch

This example can be used as a basis for situations where you want to periodically fetch a document for automated processing. Fetching a document is an asynchronous activity, so in Conkeror, we use a coroutine to write it concisely. Warning: DO NOT ABUSE THIS CODE. USE IT ONLY WHERE YOU HAVE PERMISSION.

var fetch_timer = null;

function fetch_worker () {
    var res = yield send_http_request(load_spec({uri: "http://example.com/"}));
    dumpln(res.responseText);
}

function fetch_test () {
    fetch_timer = call_at_interval(function () {
            co_call(fetch_worker());
        }, 300000); // interval of 5 minutes
}

function fetch_test_stop () {
    timer_cancel(fetch_timer);
}

To start the polling, call fetch_test. To stop the polling, call fetch_test_stop.

Further Reading