fn-context v0.2.61
fn-context
O fn-context
é um pacote que fornece um contexto de execução para funções no Node.js. Ele é útil para funções que precisam de um contexto de processo. Ou seja, não importa o quão profundo você chame uma função, o contexto de execução será o mesmo para cada processo de forma individual. O contexto é usado para gerenciar dados globais, por exemplo, estado global, serviços, configurações de usuário e muito mais.
Índice
Como usar o contexto
Usar o contexto no fn-context
requer 3 etapas simples: criar o contexto, fornecer o contexto e consumir o contexto.
1. Criando o contexto
A função de fábrica integrada createContext(default)
cria uma instância de contexto:
// context.js
import { createContext } from 'react';
export const Context = createContext('Default Value');
A função de fábrica aceita um argumento opcional: o valor padrão. Se você não fornecer um valor padrão, o valor padrão será undefined
.
2. Fornecendo o contexto
A propriedade provider
disponível na instância de contexto é usado para fornecer o contexto para as funções chamadas no escopo, não importa quão profundos eles estejam.
É uma função que requer dois argumentos:
target
: a função que será executada.defaultValue
: o valor padrão do contexto para a funçãotarget
, argumento opcional. Se não for definido, o valor padrão do contexto será o valor definido nocreateContext
.
// index.js
import { Context } from './context';
const fn = async () => {
console.log(Context.get()); // 'Default Value'
};
await Context.provider(fn)();
O que é importante aqui é que todos as funções que desejam consumir o contexto mais tarde devem estar envoltos dentro do escopo provider
.
3. Consumindo o contexto
Consumir o contexto pode ser feito de 2 maneiras.
A primeira maneira recomendada, é usar a propriedade get
disponível na instância de contexto:
// index.js
import { Context } from './context';
const fn = async () => {
console.log(Context.get()); // 'Default Value'
};
await Context.provider(fn)();
A segunda maneira é usar a propriedade value
disponível na instância de contexto:
// index.js
import { Context } from './context';
const fn = async () => {
console.log(Context.value); // 'Default Value'
};
await Context.provider(fn)();
Em caso que deseja alterar o valor do contexto, você pode usar a propriedade set
disponível na instância de contexto, ou, mudar diretamente o valor da propriedade value
:
// index.js
import { Context } from './context';
const fn = async () => {
Context.set('New Value');
// ou
// Context.value = 'New Value';
console.log(Context.get()); // 'New Value'
};
await Context.provider(fn)();
Você pode ter quantos consumidores quiser para um único contexto. Se o valor do contexto mudar (alterando a propriedade value
na instância de contexto), todos os consumidores recebem o valor atualizado. Se o valor do contexto mudar dentro de um consumidor, todos os outros consumidores recebem o valor atualizado.
Se o consumidor não estiver envolto dentro do provedor, mas ainda tentar acessar o valor do contexto, então o valor do contexto será o valor padrão fornecido como argumento para a função fábrica createContext(defaultValue)
que criou o contexto.
Quando você precisa de contexto?
A principal ideia de usar o contexto é permitir que seus processos acessem dados globais e repassem quando esses dados globais forem alterados. O contexto resolve o problema de perfuração de props: quando você precisa passar props dos pais para os filhos.
Você pode manter dentro do contexto:
- estado global
- configuração da aplicação
- nome do usuário autenticado
- configurações do usuário
- idioma preferido
- uma coleção de serviços
- ... e muito mais
Por outro lado, você deve pensar cuidadosamente antes de decidir usar contexto em sua aplicação.
Primeiro, integrar o contexto adiciona complexidade. Criar um contexto, envolver tudo em um provedor e usar value
em cada consumidor — aumenta a complexidade.
Em segundo lugar, adicionar contexto complica o teste unitário dos processos. Durante os testes, você terá que envolver as funções consumidores em um provedor de contexto. Incluindo as funções que são afetados indiretamente pelo contexto — os ancestrais dos consumidores de contexto!
Imagine-se uma situação onde você tem uma função que precisa de um contexto unificado para cada processo de forma individual. O fn-context
permite que você defina um contexto de execução para cada função e fornece uma maneira de acessar esse contexto de execução em qualquer lugar da função ou qualquer outra função que você chame dentro do escopo do contexto inicializado anteriormente, como o context.provider
.
Exemplo:
import { createContext } from 'fn-context';
const context = createContext();
const fn = async () => {
const fn2 = async () => {
console.log(context.get()); // { foo: 'bar' }
};
await fn2();
};
await context.provider(fn, { foo: 'bar' })();
Neste exemplo, a função fn2
acessa o contexto de execução definido pela função fn
. Isso é útil quando você precisa de um contexto de execução unificado para cada função.
Também funciona em caso de funções aninhadas:
import { createContext } from 'fn-context';
const context = createContext();
const fn = async () => {
const fn2 = async () => {
const fn3 = async () => {
console.log(context.get()); // { foo: 'bar' }
};
await fn3();
};
await fn2();
};
await context.provider(fn, { foo: 'bar' })();
Ou fora de escopo:
import { createContext } from 'fn-context';
const context = createContext();
const fn2 = async () => {
console.log(context.get()); // { foo: 'bar' }
};
const fn = async () => {
await fn2();
};
await context.provider(fn, { foo: 'bar' })();
Neste exemplo, a função fn2
acessa o contexto mesmo que não esteja dentro do escopo da função fn
. Porém, esse método só funcionará se a função fn2
for chamada dentro do escopo da função fn
onde foi definido o contexto com o uso do context.provider
. Caso contrário, o contexto não será acessível e retornará o valor padrão pré-definido no createContext
.
É importante que saiba que o contexto é definido apenas para a função que o inicializou. Se você chamar a função fn
em outro lugar, o contexto não será acessível e/ou retornará o valor padrão pré-definido no createContext
, como no exemplo abaixo:
import { createContext } from 'fn-context';
const context = createContext();
const fn = async () => {
console.log(context.get());
};
await context.provider(fn, { foo: 'bar' })();
await fn(); // O contexto não será acessível
É possível realizar exportação e importação do contexto para outras funções:
// context.ts
import { createContext } from 'fn-context';
export const context = createContext();
// fn.ts
import { context } from './context';
export const fn2 = async () => {
context.set({ foo: 'bar', bar: 'foo' });
};
export const fn3 = async ()=>{
console.log(context.get()); // { foo: 'bar', bar: 'foo' }
}
// index.ts
import { context } from './context';
import { fn2, fn3 } from './fn';
const fn = context.provider(async () => {
fn2();
fn3();
}, { foo: 'bar' });
await fn();
É possível criar vários contextos de execução para a mesma função ou funções diferentes:
import { createContext } from 'fn-context';
const context1 = createContext();
const context2 = createContext();
const fn = async () => {
console.log(context1.get()); // { foo: 'bar' }
console.log(context2.get()); // { bar: 'foo' }
};
await context2.provider(context1.provider(fn, { foo: 'bar' }), { bar: 'foo' })();
Uma coisa que é preciso resaltar é que, contextualizando uma função, o que se espera é que, o valor do contexto seja único para cada processo iniciado. Ou seja, se você chamar a função fn
duas vezes, o contexto será diferente para as duas chamadas. Isso é útil para funções que precisam de um contexto de execução unificado para cada processo de forma individual.
import { createContext } from 'fn-context';
const context = createContext({ foo: 'bar' });
const fn2 = async ()=>{
const valor = context.get();
context.set({...valor, time: Date.now()});
}
const fn3 = async ()=>{
return context.get();
}
const fn = async ()=>{
await fn2();
return await fn3();
};
context.provider(fn)().then(console.log); // { foo: 'bar', time: 1633661600000 }
context.provider(fn, { bar: 'foo' })().then(console.log); // { bar: 'foo', time: 1633661601000 }
O fn-context
foi pensado para uso de contextualização em desenvolvimento de APIs, inclusivemente, para o Express.js. Ou seja, conxteualizar o request para uso em várias funções que não fazem parte do escopo do request. Exemplo:
// context.ts
import { createContext } from 'fn-context';
export const reqContext = createContext({ req: null, res: null });
// controller.ts
import { reqContext } from './context';
export const getUser = async () => {
const {req} = reqContext.get();
if(!req.body.user){
req.body.user = "admin";
req.body.id = reqContext.id;
}
return req.body.user;
};
export const controller = async () => {
const {req} = reqContext.get();
if(!req.body){
req.body = {};
}
await getUser();
return req.body;
};
// index.ts
import express from 'express';
import { reqContext } from './context';
import { controller } from './controller';
const app = express();
app.get('/', reqContext.provider(async (req, res) => {
reqContext.set({req, res});
res.json(await controller());
}));
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Instalação
npm install fn-context
ou
yarn add fn-context
API
createContext
/**
* @template T - O tipo do valor padrão do contexto.
* @template C - O tipo cache do contexto, que deve ser um objeto. Por padrão, é um objeto genérico com chaves do tipo string e valores de qualquer tipo. Útil apenas em casos específicos onde você deseja armazenar valores em cache no contexto.
* @param defaultValue - O valor padrão do contexto.
* @returns Um novo contexto de execução.
*/
createContext<T, C extends Object = { [key: string]: any;}>(defaultValue?: T): Context<T>;
Propriedade responsável por criar um novo contexto de execução.
import { createContext } from 'fn-context';
const context = createContext();
Options
individual
: Se definido comotrue
, o contexto será individual para cada processo iniciado. Ou seja, se você chamar a funçãofn
duas vezes, uma dentro da outra, mas com o mesmo contexto, o conteúdo do contexto será diferente para as duas chamadas. Isso é útil uma situação que precise de um contexto diferente do antecessor. Se comofalse
, será considerado o conteúdo do contexto do antecessor. Útil em situações em que precisa de um contexto global para todas as funções chamadas. Por padrão, éfalse
.
import { createContext } from 'fn-context';
const context = createContext({ foo: 'bar' }, { individual: true });
context.provider
/**
* @template A - O tipo dos argumentos da função.
* @template R - O tipo de retorno da função.
* @param target - A função que será executada.
* @param defaultValue - O valor padrão do contexto para a função `target`. Se não for definido, o valor padrão do contexto será o valor definido no `createContext`.
* @returns Uma função que executa a função `target` com o contexto definido.
*/
provider<A extends any[], R = any | void>(target: (...args: A) => Promise<R> | R, defaultValue: T) => (...args: A) => Promise<R>;
Propriedade responsável por fornecer um contexto de execução para uma função.
import { createContext } from 'fn-context';
const context = createContext();
const fn = async () => {
console.log(context.get());
};
await context.provider(fn, { foo: 'bar' })();
context.get
/**
* @template T - O tipo do valor do contexto.
* @returns O valor do contexto.
*/
get(): T;
Propriedade responsável por obter o valor do contexto.
import { createContext } from 'fn-context';
const context = createContext();
console.log(context.get());
context.set
/**
* @template T - O tipo do valor do contexto.
* @param value - O valor do contexto.
*/
set(value: T): void;
Propriedade responsável por definir o valor do contexto.
import { createContext } from 'fn-context';
const context = createContext();
context.set({ foo: 'bar' });
context.id
/**
* @returns O ID do contexto.
*/
id: string;
Propriedade responsável por obter o ID do contexto.
import { createContext } from 'fn-context';
const context = createContext();
console.log(context.id);
context.value
/**
* @template T - O tipo do valor do contexto.
* @returns O valor do contexto.
*/
value: T;
Propriedade responsável por obter e definir o valor do contexto.
import { createContext } from 'fn-context';
const context = createContext();
console.log(context.value); // undefined
context.value = { foo: 'bar' };
console.log(context.value); // { foo: 'bar' }
context.cache
/**
* @template C - O tipo cache do contexto, que deve ser um objeto. Por padrão, é um objeto genérico com chaves do tipo string e valores de qualquer tipo. Útil apenas em casos específicos onde você deseja armazenar valores em cache no contexto.
* @returns O cache do contexto.
*/
cache: C;
Propriedade responsável por obter e definir valores no cache do contexto.
import { createContext } from 'fn-context';
const context = createContext();
console.log(context.cache.has("foo")); // false
context.cache.has
/**
* @param key - A chave do cache.
* @returns Se a chave existe no cache.
*/
cache.has(key: string): boolean;
Propriedade responsável por verificar se uma chave existe no cache do contexto.
import { createContext } from 'fn-context';
const context = createContext();
console.log(context.cache.has("foo")); // false
context.cache.get
/**
* @param key - A chave do cache.
* @returns O valor da chave no cache.
*/
cache.get(key: string): any;
Propriedade responsável por obter um valor do cache do contexto.
import { createContext } from 'fn-context';
const context = createContext();
console.log(context.cache.get("foo")); // undefined
context.cache.set
/**
* @param key - A chave do cache.
* @param value - O valor da chave no cache.
*/
cache.set(key: string, value: any): void;
Propriedade responsável por definir um valor no cache do contexto.
import { createContext } from 'fn-context';
const context = createContext();
context.cache.set("foo", "bar");
console.log(context.cache.get("foo")); // bar
context.cache.delete
/**
* @param key - A chave do cache.
*/
cache.delete(key: string): void;
Propriedade responsável por deletar um valor do cache do contexto.
import { createContext } from 'fn-context';
const context = createContext();
context.cache.set("foo", "bar");
console.log(context.cache.get("foo")); // bar
context.cache.delete("foo");
console.log(context.cache.get("foo")); // undefined
context.cache.clear
/**
* Limpa o cache do contexto.
*/
cache.clear(): void;
Propriedade responsável por limpar o cache do contexto.
import { createContext } from 'fn-context';
const context = createContext();
context.cache.set("foo", "bar");
console.log(context.cache.get("foo")); // bar
context.cache.clear();
console.log(context.cache.get("foo")); // undefined