Effection Logo

Events

Asynchronous code often needs to interact with evented code. Using async/await this can be quite challenging. Evented code often needs to be synchronous, because the timing of when to subscribe and unsubscribe is very critical, otherwise race conditions can occur where events get missed. Effection has convenient apis to ensure that you never run into these problems.

Single events

The simplest operation for working with events that Effection provides is the once() operation. This operation works with any EventTarget and blocks until one of its events occurs. For example, consider that we wanted to wait for the open event on a WebSocket, we could do something like this:

import { main, once } from 'effection';

await main(function*() {
  let socket = new WebSocket('ws://localhost:1234');

  yield* once(socket, 'open');

  console.log('socket is open!');
});

The once operation returns the argument passed to the event handler. For example we could use this to grab the code that the socket closed with:

import { main, once } from 'effection';

await main(function*() {
  let socket = new WebSocket('ws://localhost:1234');

  yield* once(socket, 'open');

  console.log('socket is open!');

  let closeEvent = yield* once(socket, 'close');
  console.log('socket closed with code', closeEvent.code);
});

Recurring events

If you've been following the chapter on streams and subscriptions, you may already have a feeling that it is not a good idea to repeatedly call once(socket, 'message') to grab the messages sent to a WebSocket. The risk here is that we miss messages if we're not very careful.

Instead we can use on(). on() is a very convenient function which takes an event target and the name of an event, and returns a Stream of values corresponding to the occurences of that event.

import { main, once, each } from 'effection';

await main(function*() {
  let socket = new WebSocket('ws://localhost:1234');

  for (let value of yield* each(on(socket, 'message'))) {
    console.log('message:', message.data);
    yield* each.next();
  }
});

This allows us not only to represent the events that fire on an event target as Effection streams, but it also lets us use all of the Stream operations to transform these streams:

import { main, pipe, map, on, filter, first } from 'effection';

await main(function*() {
  let socket = new WebSocket('ws://localhost:1234');

  let start = pipe(
    on(socket, 'message'),
    map(event => JSON.parse(event.data)),
    filter(data => data.type === 'start'),
    first,
  );

  let startEvent = yield* start;

  console.log('got start message!: ' + startEvent);
});

In this code, we very concisely:

  1. start with a stream of message events
  2. map that stream into a stream of parsed JSON payloads
  3. filter the mapped stream to only be "start" events
  4. take the filterd stream and return an operation for the first event

As a result, start is an operation that will yield the payload from the first "start" event from the websocket!

  • PreviousStreams and Subscriptions
  • NextError Handling