diff --git a/www/docs/actions.mdx b/www/docs/actions.mdx index 935e10cf..17c7c750 100644 --- a/www/docs/actions.mdx +++ b/www/docs/actions.mdx @@ -113,21 +113,10 @@ async function fetch(url) { } ``` -Consulting the "async rosetta stone" below, we can substitute the async +Consulting the [Async Rosetta Stone](), we can substitute the async constructs for their Effection counterparts to arrive at a line for line translation. -| Async | Effection | -|-----------------|-----------------------| -|`Promise` | `Operation` | -|`new Promise()` | `action()` | -|`await` | `yield*` | -|`async function` | `function*` | -|`AsyncIterable` | `Stream` | -|`AsyncIterator` | `Subscription` | -|`for await` | `for yield* each` | - - ```js function* fetch(url) { return yield* action(function*(resolve, reject) { diff --git a/www/docs/async-rosetta-stone.mdx b/www/docs/async-rosetta-stone.mdx new file mode 100644 index 00000000..02e00834 --- /dev/null +++ b/www/docs/async-rosetta-stone.mdx @@ -0,0 +1,12 @@ + + +| Async/Await | Effection | +| ---------------- | ----------------- | +| `Promise` | `Operation` | +| `new Promise()` | `action()` | +| `await` | `yield*` | +| `async function` | `function*` | +| `AsyncIterable` | `Stream` | +| `AsyncIterator` | `Subscription` | +| `for await` | `for yield* each` | + diff --git a/www/docs/structure.json b/www/docs/structure.json index 2e39843d..6102eacd 100644 --- a/www/docs/structure.json +++ b/www/docs/structure.json @@ -2,14 +2,18 @@ "Getting Started": [ ["introduction.mdx", "Introduction"], ["installation.mdx", "Installation"], + ["typescript.mdx", "TypeScript"], + ["thinking-in-effection.mdx", "Thinking in Effection"], + ["async-rosetta-stone.mdx", "Async Rosetta Stone"] + ], + "Learn Effection": [ ["operations.mdx", "Operations"], ["actions.mdx", "Actions and Suspensions"], ["resources.mdx", "Resources"], ["spawn.mdx", "Spawn"], ["collections.mdx", "Streams and Subscriptions"], ["events.mdx", "Events"], - ["errors.mdx", "Error Handling"], - ["typescript.mdx", "TypeScript"] + ["errors.mdx", "Error Handling"] ], "Advanced": [ ["scope.mdx", "Scope"], diff --git a/www/docs/thinking-in-effection.mdx b/www/docs/thinking-in-effection.mdx new file mode 100644 index 00000000..6ab6b1c6 --- /dev/null +++ b/www/docs/thinking-in-effection.mdx @@ -0,0 +1,113 @@ +When we say that Effection is "Structured Concurrency and Effects for Javascript" we mean three things: + +1. No operation runs longer than its parent. +2. Every operation exits fully. +3. 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 because memory lifetime is bound to the scope of +the function that it was allocated for. When a function is finished, its scope is torn down and all of the memory +allocated to variables in function's scope are 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 asynchrony. Every Effection operation is +bound to the lifetime of its parent. Because of this, 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 where and when to run +asynchronous cleanup. + +The key to achieving this freedom is to make the following mental shift about the natural lifetime of an asynchronous +operation. + +**before** + +> An asynchronous operation will run as long as it needs to + +**after** + +> An asynchronous operation runs only as long as it's needed. + +Effection provides this shift. Whenever an operation completes, none of its child operations are left +around to pollute your runtime. + +## Every operation exits fully. + +We expect synchronous functions to run completely from start to finish. + +```js {5} showLineNumbers +function main() { + try { + fn() + } finally { + // code here is GUARANTEED to run + } +} +``` + +Knowing this 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. + +However, the same guarantee is not provided for async functions. + +```js {5} showLineNumbers +async function main() { + try { + await new Promise((resolve) => setTimeout(resolve, 100,000)); + } finally { + // code here is NOT GUARANTEED to run + } +} + +await main(); +``` + +Once an async function begins execution, the code in its `finally{}` blocks may never get +a chance to run, and as a result, it is difficult to write code that always cleans up after itself. + +This hard limitation of the JavaScript runtime is called the [Await Event Horizon][await-event-horizon], +and developers experience its impact on daily basis. For example, the very common [EADDRINUSE][eaddrinuse-error] +error is caused by a caller not being able to execute clean up when a Node.js process is stopped. + +By contrast, Effection _does_ provide this guarantee. + +```js {7} showLineNumbers +import { main, action } from "effection"; + +await main(function*() { + try { + yield* action(function*(resolve) { setTimeout(resolve, 100,000) }); + } finally { + // code here is GUARANTEED to run + } +}); +``` + +When executing Effection operations you can expect that +they 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. + +However, one of the constructs we avoid in Effection is the syntax and semantics of `async/await`. +This is because `async/await` is [not capable of modeling structured concurrency][await-event-horizon]. Instead, we make +extensive use of [generator functions][generator-functions] which are a core feature of JavaScript and are supported by all browsers +and JavaScript runtimes. + +Finally, we provide a handy Effection Rosetta Stone to show how _Async/Await_ concepts map into Effection APIs. + +[generator-functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* +[await-event-horizon]: https://frontside.com/blog/2023-12-11-await-event-horizon/ +[delimited-continuation-repo]: https://github.com/thefrontside/continuation +[eaddrinuse-error]: https://stackoverflow.com/questions/14790910/stop-all-instances-of-node-js-server