Effection Logo

Thinking in Effection

There are two fundamental ideas that will help you get the most of Effection.

  1. Structured Concurrency
  2. It's just JavaScript

Structured Concurrency

One way to think about Structured Concurrency is that it offers developers the same guarantees for their asyncronous code as they expect from syncronous code. These guarantees are that a child function will never outlive it's parent and every function runs to completion. When we write synchrous code in JavaScript we take these guarantees for granted which makes our code easier to write and think about. Effection is designed to give you these guarantees with as little friction as possible.

Child function will never outlive it's parent

In syncronous code, when you call a function from another function, you know that the child function will complete before the parent proceeds. For example, the following code will output before child, child and after child every time. This order is guaranteed by the JavaScript runtime.

function child() {
  console.log("child");
}

function parent() {
  console.log("before child");
  child();
  console.log("after child");
}

parent();

JavaScript runtime provides no predictable or reliable guarantees on what happens if the child function calls an asynchronous operation. For example, if we wrap the console.log('child') in setTimeout for 1 millisecond. The result will be before child, after child and child which means that the child function was still executing while the parent function was finished.

function child() {
  setTimeout(() => console.log("child"), 1);
}

This happens because the JavaScript runtime does not guarantee the child function will never outline it's parent. Effection brings this Structured Concurrency guarantee to the JavaScript runtime environment. The same example implemented in Effection behaves according to the guarantees of Structure Concurrency.

import { sleep, run } from "effection";

function* child() {
  yield* sleep(1);
  console.log("child");
}

function* parent() {
  console.log("before child");
  yield* child();
  console.log("after child");
}

await run(parent);

The above example will output before child, child and after child as we would expect.

💡 You might assume that Effection makes everything asyncronous which is incorrect. Effection is built on Deliminated Continuation which allows us to treat syncronous and asyncronous code in the same way without making synchronous code asynchronous.

Every function runs to completion

We expect synchronous functions to run to completion. This guarantee is used by the JavaScript runtime to perform garbage collection by releasing any memory allocated during execution of a function. For example, a function that defines a variable will leave no trace of that variable once that function is finished. Once fn() is finished executing we take it for granted that any resources consumed by that function are released.

function fn() {
  const value = 5;
}

fn();

JavaScript runtime does not offer the same guarantees for asynchronous code. In JavaScript, you may never know if an asyncronous process is truely finished.

// TODO: ADD EXAMPLE HERE

It's just JavaScript

Effection is designed to provide Structured Concurrency guarantees using common JavaScript language constructs such as let, const, if, for, while, switch and try/catch/finally. Our goal is to allow JavaScript developers to leverage what they already know while gaining benefits of Structured Concurrency. This means that you can use

Async Rosetta Stone

AsyncEffection
PromiseOperation
new Promise()action()
awaityield*
async functionfunction*
AsyncIterableStream
AsyncIteratorSubscription
for awaitfor yield* each
  • PreviousTypeScript