Context
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. This allows you to override the context value of an operation in the tree without affecting the value higher up the tree.
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);
}
Use cases for context
- Config: most apps written with Effection require values that come from the environment variables. Use can use context to store values from configuration to easily retrieve it in any operation.
- Client APIs: many APIs require creating a client instance. You can create the client instance once and make it available to all operations using context.
Naming conventions
The following is absolutely optional, but if you don't know how to organize your context related code, try the following:
For the reference
Add Context
to the end of the context reference. This makes it easy to identify context references. For example, a config context might be called ConfigContext
.
import { createContext } from 'effection';
const ConfigContext = createContext('config');
For the initializer
Provide an init
function for initializing context. You'll typically call this once at the beginning of your program.
import { main } from 'effection';
function* initConfigContext() {
yield* ConfigContext.set({
mySecret: process.env.MY_SECRET
})
}
await main(function* () {
yield* initConfigContext();
});
For the use helper
Provide a use
helper for reading the context. Provide a helper for retrieving the value from the context with use
prefix.
function* useConfig() {
return yield* ConfigContext;
}
function* someOperation() {
const config = yield* useConfig();
}