- Add new doc on Storage module - Improve formatting in the JS section for better readability
5.1 KiB
Event Loop module
The event loop is central to event-based programming in many frameworks, and our JS subsystem is no exception. It is a good idea to familiarize yourself with the event loop first before using any of the advanced modules (e.g. GPIO and GUI).
let eventLoop = require("event_loop");
Conceptualizing the event loop
If you've ever written JavaScript code before, you've definitely seen callbacks. It's when a function takes another function (usually an anonymous one) as one of the arguments, which it will call later, e.g. when an event happens or when data becomes ready:
setTimeout(function() { console.log("Hello, World!") }, 1000);
Many JavaScript engines employ a queue from which the runtime fetches events as they occur, subsequently calling the corresponding callbacks. This is done in a long-running loop, hence the name "event loop". Here's the pseudocode for a typical event loop:
\code{.js} while(loop_is_running()) { if(event_available_in_queue()) { let event = fetch_event_from_queue(); let callback = get_callback_associated_with(event); if(callback) callback(get_extra_data_for(event)); } else { // avoid wasting CPU time sleep_until_any_event_becomes_available(); } } \endcode
Most JS runtimes enclose the event loop within themselves, so that most JS programmers don't even need to be aware of its existence. This is not the case with our JS subsystem.
Example
This is how one would write something similar to the setTimeout
example above:
// import module
let eventLoop = require("event_loop");
// create an event source that will fire once 1 second after it has been created
let timer = eventLoop.timer("oneshot", 1000);
// subscribe a callback to the event source
eventLoop.subscribe(timer, function(_subscription, _item, eventLoop) {
print("Hello, World!");
eventLoop.stop();
}, eventLoop); // notice this extra argument. we'll come back to this later
// run the loop until it is stopped
eventLoop.run();
// the previous line will only finish executing once `.stop()` is called, hence
// the following line will execute only after "Hello, World!" is printed
print("Stopped");
I promised you that we'll come back to the extra argument after the callback
function. Our JavaScript engine does not support closures (anonymous functions
that access values outside of their arguments), so we ask subscribe
to pass an
outside value (namely, eventLoop
) as an argument to the callback so that we
can access it. We can modify this extra state:
// this timer will fire every second
let timer = eventLoop.timer("periodic", 1000);
eventLoop.subscribe(timer, function(_subscription, _item, counter, eventLoop) {
print("Counter is at:", counter);
if(counter === 10)
eventLoop.stop();
// modify the extra arguments that will be passed to us the next time
return [counter + 1, eventLoop];
}, 0, eventLoop);
Because we have two extra arguments, if we return anything other than an array of length 2, the arguments will be kept as-is for the next call.
The first two arguments that get passed to our callback are:
- The subscription manager that lets us
.cancel()
our subscription. - The event item, used for events that have extra data. Timer events do not,
they just produce
undefined
.
API reference
run()
Runs the event loop until it is stopped with stop
.
subscribe()
Subscribes a function to an event.
Parameters
contract
: an event source identifiercallback
: the function to call when the event happens- extra arguments: will be passed as extra arguments to the callback
The callback will be called with at least two arguments, plus however many were
passed as extra arguments to subscribe
. The first argument is the subscription
manager (the same one that subscribe
itself returns). The second argument is
the event item for events that produce extra data; the ones that don't set this
to undefined
. The callback may return an array of the same length as the count
of the extra arguments to modify them for the next time that the event handler
is called. Any other returns values are discarded.
Returns
A SubscriptionManager
object:
SubscriptionManager.cancel()
: unsubscribes the callback from the event
Warning
Each event source may only have one callback associated with it.
stop()
Stops the event loop.
timer()
Produces an event source that fires with a constant interval either once or indefinitely.
Parameters
mode
: either"oneshot"
or"periodic"
interval
: the timeout (for"oneshot"
) timers or the period (for"periodic"
timers)
Returns
A Contract
object, as expected by subscribe
's first parameter.
queue()
Produces a queue that can be used to exchange messages.
Parameters
length
: the maximum number of items that the queue may contain
Returns
A Queue
object:
Queue.send(message)
:message
: a value of any type that will be placed at the end of the queue
input
: aContract
(event source) that pops items from the front of the queue