Thinking in Effection
When we say that Effection is "Structured Concurrency and Effects for Javascript" we mean three things:
- No operation runs longer than its parent.
- Every operation exits fully.
- It's just JavaScript, and except for the guarantees derived from (1) and (2), it should feel familiar in every other way.
Developing a new intuition about how to leverage Structured Concurrency, while leaning on your existing intuition as a JavaScript developer will help you get the most out of Effection and have you attempting things that you would never have even dreamed before.
No operation runs longer than its parent.
In JavaScript, developers rarely have to think about memory allocation within a function because the lifetime of memory allocation is bound to the scope of the function. When a function is finished, it's scope is torn down by the JavaScript runtime and all of the memory allocated to variables in function's scope can be released safely. Binding memory management to scope gives developers the freedom to focus on their application instead worrying about leaking memory.
Structured concurrency establishes the same relationship between scope and operations. Every Effection operation is bound to the lifetime of it's parent. An operation can not outlive its parent and Effection automatically tears down child operations when the parent operation completes or is halted. Like with memory management, binding asynchrony to scope frees developers to focus on writing their applications instead of worrying about out of control asyncronous operations.
The key to the freedom from worrying about asyncrony is to make the mental shift from an asynchronous function will take as long as it needs to _an asyncronous operation will run as long as it's neeeded by its parent operation." Effection provides you with the guarantee that when a parent operation completes, not child operation is left polluting your runtime.
💡 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 operations in the same way without making synchronous operations asynchronous.
Every operation exits fully.
We expect synchronous functions to run completely from start to finish. Knowing that your function full run to
completion makes code predictable. Developers can be confident that their functions will either return a result or
throw an error. You can wrap a synchronous function in a try/catch/finally
block to handle thrown errors. The
finally
block can be used to perform clean up after completion.
async function main() {
try {
await new Promise((resolve) => setTimeout(resolve, 100,000));
} finally {
// code here is NOT GUARANTEED to run
}
}
await main();
The same guarantee is not provided for async functions. Once an async function is execute, the code after may never get a chance to run. This makes it difficult to write code that cleans up after itself. This limitation of the JavaScript runtime is described in greater detail in the Await Event Horizon in JavaScript blog post. Developers experience the impact of this on daily basis. Many EADDRINUSE errors are caused by caller not being able to execute clean up when a Node.js process is stopped.
import { main, action } from "effection";
await main(function*() {
try {
yield* action(function*(resolve) { setTimeout(resolve, 100,000) });
} finally {
// code here is GUARANTEED to run
}
});
Effection brings this guarantee to your JavaScript runtime. When executing Effection operations you can expect that Effection operations will run to completion giving every operation an opportunity to clean up. At first glance, this might seem like a small detail but it's fundamental to writing composable code.
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 the guarantees of Structured Concurrency. You can use all of these
constructs in an Effection function and they'll behave as you'd expect.
The one area where Effection can not provide Structured Concurrency guarantees is in runtime behaviour of async/await. We explained why in The Await Event Horizon in JavaScript blog post. Instead of async/await we use generator functions which are supported by all browsers and JavaScript runtimes.
We provide a handy Effection Rosetta Stone to show how Async/Await concepts map into Effection APIs.