Context
Effection contexts store ambient values that are needed by the operations in your application in order to run. Some of the most common
use cases of Context
are
- Configuration: most apps require values that come from the environment such as evironment variables or configuration files. Use context to easily retrieve such values from within any operation.
- Client APIs: many APIs provide client libraries to connect with them. With an Effection context, you can create a client instance once and share it between all child operations.
- Services: Database connections, Websockets, and many other types of APIs involve a handle to a stateful object that needs to be destroyed accordingly to a set lifecycle. Combined with resource api, Effection context allows you to share a service between child operations and also to guarantee that it is disposed of properly when it is no longer needed.
The problem
Usually, you can pass information from a parent operation to a child operation
via function argument or lexical scope. But passing function arguments can become
verbose and inconvenient when the you have to pass them through many operations in
the middle, or if many operations need the same information. Likewise, passing information
via lexical scope is only possible if you define the child operation in the body of the
parent operation. Context
lets the parent operation make some information available to any
operation in the tree below it-no matter how deep-without passing it explicitely through function
arguments or lexical scope.
💁 If you're familiar with React Context, you already know most of what you need to know about Effection Context. The biggest difference is the API but general concepts are same.
What is argument drilling?
Passing argument to operations is a convenient way to make data from parent operation available to the child operation.
But passing arguments can become inconvenient when the child operation is nested deeply in the tree of operations, or the same arguments need to be passed to many operations. This situation is called "argument drilling".
Wouldn't it be great if you could access information in a deeply nested operation from a parent operation without modifying operations in the middle? That's exaclty what Effection Context does.
Context: an alternative to passing arguments
Context makes a value available to any child process within a tree of processes.
import { createContext, main } from 'effection';
// 1. create the context
const GithubTokenContext = createContext("token");
await main(function* () {
// 2. set the context value
yield* TokenContext.set("gha-1234567890");
yield* fetchGithubData();
})
function* fetchGithubData() {
yield* fetchRepositories();
}
function* fetchRepositories() {
// 3. use the context value in a child operation
const token = yield* GithubTokenContext;
console.log(token);
// -> gha-1234567890
}
Context: overriding nested context
Context is attached to the scope of the parent operation. That means that the operation and all of its children will see that same context.
However, if a child operation sets its own value for the context, it will not affect the value of any of its ancestors.
Using Context
To use context in your operations, you need to do the following 3 steps:
- Create a context.
- Set the context value.
- Yield the context value.
Step 1: Create a context.
Effection provides a function for creating a context appropriatelly called createContext
. This function will return
a reference that you use to identify the context value in the scope.
import { createContext } from 'effection'
const MyValueContext = createContext("my-value");
Step 2: Set the context value.
await main(function* () {
yield* MyValueContext.set("Hello World!");
});
Step 3: Yield the context value.
await main(function* () {
yield* MyValueContext.set("Hello World!");
yield* logMyValue();
});
function* logMyValue() {
const value = yield* MyValueContext;
console.log(value);
}