Proposta para adicionar Signals ao JavaScript
(github.com/proposal-signals)- A proposta JavaScript Signals do TC39 é uma direção inicial para padronizar primitivos reativos para rastrear com eficiência estado de UI e estado computado, e atualmente é um rascunho em Stage 1
- A proposta foca mais na semântica central do grafo de Signals e no mecanismo de rastreamento automático que frameworks podem compartilhar do que em uma API de superfície voltada diretamente para desenvolvedores de aplicações
Signal.State,Signal.ComputedeSignal.subtle.Watchersão as APIs principais, e os cálculos buscam avaliação preguiçosa, cache, rastreamento automático de dependências e execução sem glitches- O objetivo dos Signals embutidos é aumentar a interoperabilidade entre vários frameworks como Angular, Ember, MobX, Preact, Qwik, Solid, Svelte e Vue, além de abrir possibilidades para suporte de depuração e análise de desempenho em DevTools
- O grupo da proposta pretende passar por vários polyfills de nível de produção, integração com frameworks, validação em aplicações de grande escala e benchmarks de desempenho antes do Stage 2, e a padronização pode levar pelo menos mais de 2 a 3 anos
Onde a proposta JavaScript Signals se encaixa
- JavaScript Signals é apresentada como uma proposta Stage 1 no processo do TC39
- O documento atual é uma tentativa de alinhar uma direção comum para o ecossistema JavaScript, de forma parecida com o que Promises/A+ representou antes da padronização de
Promiseno ES2015 - Há um polyfill disponível para experimentação direta
- Os champions da proposta e autores originais montaram o rascunho atual com base em contribuições de design de vários frameworks e bibliotecas
O problema que a padronização quer resolver
- UIs complexas precisam armazenar valores, calcular, invalidar, sincronizar e enviar atualizações para a camada de visualização, e os Signals querem fornecer uma infraestrutura de gerenciamento de estado para esse trabalho repetitivo
- Em um exemplo com Vanilla JS,
counter,isEven,parityerenderficam diretamente acoplados, gerando os seguintes problemas- O estado e o sistema de renderização ficam fortemente acoplados
- Mesmo quando
paritynão muda, como no caso em quecountervai de 2 para 4, ainda ocorrem cálculos e renderizações desnecessários - Quando diferentes partes da UI querem assinar apenas alguns entre
counter,isEveneparity, gerenciar manualmente inscrição e cancelamento se torna complexo - Adicionar pub/sub em várias etapas aumenta boilerplate, bookkeeping de assinaturas e o risco de vazamentos de memória
- O exemplo baseado em Signals usa
Signal.StateeSignal.Computedpara tratar valores, cálculos e efeitos colaterais de forma unificada- Não exige inscrições manuais
- Encontra automaticamente de quais Signals um Signal computado depende
- Os cálculos só são executados quando o valor é explicitamente solicitado
- O Signal computado guarda em cache o último valor
APIs principais da proposta
Signal<T>é definido como um valor legível comget(): TSignal.State<T>é um Signal gravável- O construtor recebe um valor inicial e opções
- O valor é lido com
get()e alterado comset(t)
Signal.Computed<T>é um Signal computado baseado em outros Signals- É calculado a partir do valor retornado por um callback
- Faz rastreamento automático de dependências
- O valor é calculado de forma lazy e mantido em cache
Signal.subtlereúne APIs avançadas, mais próximas de autores de frameworks ou implementações de DevToolsuntrack(cb)permite ler um Signal sem rastreamentocurrentComputed()retorna o computed Signal atualmente em rastreamentointrospectSources,introspectSinks,hasSinksehasSourcessão APIs para observar o grafoWatcheré a base para implementar effects e agendamento em nível de framework ao detectar mudanças de Signals
SignalOptions<T>oferece suporte a uma função de comparação personalizadaequalse hookswatched/unwatched
Como funciona e modelo de execução
- Um Signal representa uma célula de dados que pode mudar ao longo do tempo, dividida em
stateoucomputed - Um Signal computado registra automaticamente os Signals lidos durante sua execução e, quando for lido depois, verifica se dependências anteriores mudaram
- O cálculo é baseado em pull
- Mesmo que dependências mudem, ele não recalcula imediatamente
- Ele recalcula quando necessário, no momento em que alguém o lê com
.get()
- Escritas em State Signals são refletidas de forma síncrona
- Depois de
.set(), se um computed Signal dependente for lido, ele recalcula imediatamente se necessário - Não há batching embutido
- Depois de
- O callback
notifyde um Watcher pode ser executado de forma síncrona durante.set()- Porém, não é permitido ler nem escrever Signals dentro de
notify - Operações reais de leitura e escrita devem ser agendadas depois
- Porém, não é permitido ler nem escrever Signals dentro de
- Se o callback de um computed Signal lançar uma exceção, essa exceção também fica em cache como se fosse um valor e é lançada novamente quando o Signal for relido
Motivação para a padronização
- As implementações de Signals em cada framework têm seu próprio mecanismo de rastreamento automático, o que dificulta compartilhar modelos, componentes e bibliotecas entre frameworks
- O objetivo da proposta é separar o modelo reativo da view de renderização
- Isso evita que desenvolvedores precisem reescrever código não relacionado à UI ao trocar de tecnologia de renderização
- Também permite criar em JavaScript modelos reativos compartilháveis em vários contextos
- Em termos de desempenho e memória, o documento afirma que uma implementação embutida pode ser mais eficiente do que uma em JS por um pequeno fator constante, mas deixa claro que o engine não vai mudar algoritmos magicamente
- Do ponto de vista de DevTools, Signals embutidos poderiam mostrar melhor as seguintes informações
- A call stack de cadeias de computed Signals
- O grafo de referências entre Signals
- Relações de dependência necessárias para depurar uso de memória
- Caso entrem na biblioteca padrão, também se esperam efeitos colaterais positivos, como redução do tamanho de bundle, melhora de estabilidade e qualidade, e formação de um vocabulário comum entre projetos
Objetivos e restrições de design
- As capacidades centrais incluem Signal gravável, Signal computado, reação a estado dirty, agendamento próprio de framework,
untracke composição entre múltiplos codebases - Signals computados buscam ser glitch-free
- Para evitar cálculos desnecessários, partes potencialmente dirty do grafo são executadas em ordem topológica
- A proposta tenta eliminar cálculos duplicados
- Não há um agendamento forçado embutido no estilo Promise, para que frameworks possam manter seu próprio modelo de scheduling
- Para evitar uso indevido de callbacks de reação síncrona, a leitura e escrita de Signals são proibidas dentro de
notifyde um Watcher untracké tratado como uma rota de escape insegura- Se um Signal lido sem rastreamento influenciar o resultado do cálculo, mudanças nesse Signal podem não atualizar o computed Signal
- A API prioriza servir de base para implementações de framework, e não foi desenhada especialmente para ser ergonômica para desenvolvedores de aplicações em geral
effect e Watcher
- A proposta não inclui uma função embutida como
effect() - O agendamento de effects se mistura com ciclo de renderização do framework, descarte e gestão de ownership, então a API padrão do JavaScript não tenta resolver isso diretamente
- Em vez disso,
Signal.subtle.Watcherfornece a base de baixo nível para implementar effectsnotifyé chamado quando dependências de um Signal observado mudamgetPending()permite verificar quais Signals ainda estão dirty- Effects que precisam de descarte devem ser limpos com
unwatch
- Um Signal observado por um Watcher pode continuar vivo enquanto seu estado interno estiver alcançável, então a limpeza de effects exige chamar
Watcher.prototype.unwatch
Funcionalidades ausentes no rascunho atual
- Async não está incluído no modelo atual
- Signals são sempre tratados como valores avaliáveis de forma síncrona
- Há alguma possibilidade de modelar estado de loading com exceções, e a discussão de melhorias está na Issue #30
- Transactions também não estão incluídas
- Para manter ao mesmo tempo os estados “from” e “to” em uma transição de tela, surge o problema de fazer fork do estado do grafo de Signals
- A discussão relacionada está na Issue #73
- Alguns convenience methods também estão ausentes do rascunho atual
- Essas funcionalidades ficaram de fora por falta de consenso entre frameworks e porque podem ser contornadas em camadas superiores, mas podem ser reavaliadas depois dos protótipos
Plano de desenvolvimento e cronograma de padronização
- A proposta entrou na pauta do TC39 Stage 1 em abril de 2024, e o documento explica que, na prática, ainda pode ser vista como algo próximo de Stage 0
- Antes de propor o Stage 2, o plano inclui os seguintes trabalhos
- Desenvolver várias implementações de polyfill em nível de produção
- Testar em diferentes frameworks e passar em testes no estilo test262
- Validar desempenho com um conjunto robusto de benchmarks de Signals/frameworks
- Integrar a API proposta a vários frameworks JS representativos e a algumas aplicações de grande escala
- Entender a capacidade de extensão da API e decidir o que deve ou não ser incluído
- O grupo da proposta quer avançar de forma conservadora para evitar que uma forma inadequada de Signals seja padronizada cedo demais
- O FAQ estima que Signals padronizados só devem ser usados amplamente em navegadores sem polyfill em pelo menos 2 a 3 anos
- O polyfill atual já pode ser usado, mas o documento recomenda não depender da estabilidade da API, já que ela ainda pode mudar durante a revisão
Modelo de uso resumido no FAQ
- Signals embutidos são independentes da tecnologia de renderização
- O documento explica que isso funciona tanto para modelos como o VDOM do Preact, o DOM nativo do Solid e a abordagem híbrida do Vue
- Para desenvolvedores de aplicações, em geral faz mais sentido usar Signals por meio de um framework
- O framework gerencia Watcher,
untrack, ownership, disposal e o agendamento da renderização do DOM
- O framework gerencia Watcher,
- Eles podem ser usados junto com SSR, hydration e resumability
- O Qwik já usa Signals com essas propriedades, e a proposta entende que os Signals resumable do Qwik podem ser modelados com uma combinação de State e Computed
- Signals e
Proxysão complementaresProxyintercepta operações rasas em objetos, enquanto Signals coordenam o grafo de dependências de células de dados- Colocar Signals atrás de um
Proxypode tornar uma estrutura reativa aninhada mais ergonômica
- Signals não são streams, e sim células que representam o valor atual
- Se um State Signal receber duas escritas seguidas e nada mais acontecer, a primeira escrita pode não ser visível para computed Signals ou effects
- O documento trata isso como uma característica no lado oposto da execução glitch-free e diz que, para streams, outras construções como async iterable ou observable são mais adequadas
1 comentários
Opiniões do Hacker News
Será que sou só eu que acho que o exemplo em JavaScript puro é, na verdade, mais fácil de ler e manipular?
Dizem que “a configuração tem muito ruído e boilerplate”, mas o exemplo com signals parece igualmente barulhento e cheio de boilerplate, além de acrescentar um conceito novo difícil para iniciantes entenderem
A ideia de que “se o counter muda de 2 para 4, a parity não muda, mas há cálculo e renderização desnecessários” soa como memoização prematura
Se outra parte da UI quiser renderizar em resposta a atualizações do counter, é verdade que aquele exemplo strawman não é adequado; nesse caso, dá para usar outros métodos, como signals, tratamento de eventos ou um armazenamento central de estado (estilo Redux)
Se outra parte da UI depende apenas de isEven ou parity, dá para mudar a abordagem caso isso seja a estrutura central do app, mas na maioria das vezes não é. A ideia de que “uma função render que depende apenas de parity precisa saber que deve assinar counter” também não é necessariamente um fardo injusto, e funções de cálculo puras têm a vantagem de tornar fácil identificar as entradas
Tentar padronizar signals, um conceito cada vez mais usado no desenvolvimento de UI, é algo louvável. Deixando de lado debates de detalhe, como quanto boilerplate há ou se é preciso criar um sistema de eventos por conta própria, se vários frameworks estão usando signals, pode haver um motivo; mesmo que leve tempo, vale tentar padronizar
https://preactjs.com/guide/v10/signals
No Preact, quando um signal desce pela árvore via props ou context, apenas a referência do signal é passada, e como o componente observa o signal, não o valor, atualizar o signal pode não renderizar o componente novamente. Na prática, é possível ir diretamente ao componente da árvore que acessa
.valueAlém disso, um signal rastreia quando o valor é acessado e quando é atualizado; no Preact, se você acessa
.valuede um signal dentro de um componente, esse componente é automaticamente renderizado de novo quando o valor daquele signal mudaPela minha experiência, a grande vantagem é poder modularizar estado reativo. No estilo imperativo, é preciso estado adicional para acompanhar mudanças, e a modularidade é alcançada por meio de abstração. Basta usar quando for necessário
Criar um exemplo simples e ainda aplicável é uma questão de equilíbrio. Os casos em que a reatividade traz um benefício claro costumam ser mais complexos, por isso são mais difíceis de demonstrar do que exemplos simples, mas menos aplicáveis
Quando Promises foram adicionadas ao JavaScript, eu tinha certa resistência, achando que seria preciso usar
new Promisepor toda parteNa prática, consigo contar nos dedos das duas mãos quantas vezes escrevi
new Promisediretamente. Em vez disso, especialmente ao lidar com bibliotecas de terceiros, acabei usando.thencom muito mais frequênciaNo fim, o efeito cotidiano de adicionar Promise ao JavaScript foi fornecer uma interface bastante simples, em geral robusta e quase universal para vários comportamentos e recursos especiais oferecidos por bibliotecas de terceiros. Seja leitura de arquivos, requisições de API ou saída de etapas de build, ao usar
.then(res => …)parece que você já está a meio caminho de algo funcionalSe esta proposta de Signal cumprir um papel parecido em meio à explosão cambriana dos frameworks de UI reativos, sou a favor. Mais ainda, ela também pode ajudar a reatividade a se expandir para fora da UI. Muitas vezes imaginei árvores de estado de recálculo incremental para coisas que não são estado de UI
new PromisediretamenteO
.thendas Promises iniciais foi uma grande melhoria em relação a delegates aninhados e funciona bem para cadeias simples, mas quando é preciso encadear Promises diferentes condicionalmente, ter tratamento de erro distinto para cada cadeia ou fazer retorno antecipado, o código pode ficar muito mais difícil de ler e manipularCom async/await, dá para escrever chamadas como se não fossem Promises, colocar try/catch facilmente em torno de uma chamada específica de Promise e fazer retornos antecipados de forma natural
Não entendo por que isso deveria se tornar parte da linguagem. Dá para fazer com bibliotecas, e já existem bibliotecas desse tipo. Como são pequenas, não é um grande fardo incluí-las no código, e adicionar algo à linguagem não deveria ser um objetivo em si
É arrogante achar que as bibliotecas de UI em JS de hoje projetaram signals tão bem que eles deveriam virar parte da linguagem. Há muitas implementações de signals com diferentes trade-offs, e nenhuma delas merece um lugar especial na especificação do JavaScript
Antes de essas bibliotecas usarem signals, elas usavam DOM virtual. Felizmente o DOM virtual não virou parte do JS; o que há de diferente em signals? Nada. O argumento para padronizá-los é ainda mais fraco do que era no caso do DOM virtual
Vamos enfiar tudo que estiver na moda em um runtime no qual, na prática, não há como remover recursos que não queremos mais sem quebrar a web? É bem míope
UI reativa venceu. Mesmo em aplicações pequenas, quando se faz gerenciamento de estado, a complexidade explode, e esse é o ponto central que torna difícil usar JS puro. Para mim, qualquer framework reativo é melhor do que JS puro; sendo assim, talvez esteja faltando algum componente
Já se passaram cerca de 10 anos, então é hora de pensar em um ponto de corte que possa ser padronizado. Se for feito direito, como Promise, pode reduzir a complexidade de casos de uso muito comuns
Uma avaliação melhor é perguntar: “os frameworks reativos existentes usariam esta proposta?”. Se não, é preciso ver por quê, o que está faltando, o que é desnecessário e o que podemos aprender com UI e reatividade em outras linguagens. Vale a pena refinar essas experiências dispersas
Dá para discutir essa necessidade, mas, se a biblioteca padrão vai ser expandida, olhar para o que é popular me parece uma boa abordagem
signals não são um substituto do DOM virtual
Quando é preciso sinalizar algo para a aplicação inteira, usa-se eventos
window.dispatchEvent(new Event('counterChange'));E qualquer parte da aplicação que quiser reagir pode se inscrever assim
window.addEventListener('counterChange', () => { ... do something ... });Qual é o problema dessa abordagem?
Tratamento de eventos fica bagunçado com muita facilidade. Para ver mais a fundo, basta olhar para bubbling e propagação de eventos
Aplicações grandes precisam de tratamento de eventos robusto, e essa é uma das vantagens hoje menos evidentes de frameworks como Angular e Vue
Você provavelmente não vai querer usar diretamente a API padrão de tratamento de eventos sem um framework. Ao lidar com adicionar, excluir, duplicar, disparar, remover, disparar uma única vez etc. para muitos elementos, podem surgir efeitos colaterais indesejados sérios
A diferença em relação a signals é que o valor resultante só é calculado quando o consumidor final lê o valor. O momento em que se escreve de fato no signal é separado do agendamento de atualizações assíncronas de renderização, e a cadeia de cálculos executada pelo observador roda apenas uma vez durante a renderização
Os valores intermediários enviados pelo signal desaparecem, então é difícil fazer muita coisa interessante dentro deles; na prática, é mais uma camada de abstração avançada para coordenar o ciclo de renderização
O desempenho também pode ser melhor. Por exemplo, se houver um cálculo que depende de dois valores, em
result = a ? b : 0, se a for falso, não é preciso recalcular quando b mudar. Com signals isso acontece automaticamente, mas com publicação/assinatura tradicional seria necessário bastante códigoTambém é difícil garantir que todos os listeners não criem esse tipo de disparo em cadeia
Durante décadas, as pessoas vêm tentando entender por que rastreamento de estado e atualizações do DOM são tão difíceis
Claro que é preciso um pouco de disciplina, mas isso parece muito mais simples do que as soluções que surgem a cada poucos anos. Backbone, Knockout, Angular, React, modificar a própria linguagem etc.; talvez meu modo de pensar seja fundamentalmente diferente
Isso aparece até no nome das funções. Chamar a atualização de
innerTextde “render” não é realmente renderizar. No máximo, quem renderiza é o navegador, e o mesmo vale para todo o resto relacionado a pintura. Parece uma tentativa desesperada de complicar uma das funções mais simples do DOM, o que é realmente desconcertanteQuando fica mais complexo, deixa de ser fácil
O progresso da programação pode ser visto como um processo de eliminar a disciplina ritualística e rígida necessária para obter bons resultados
Não quer dizer que React seja a próxima evolução, mas signals certamente são um passo na direção certa
Sincronizar o DOM com o estado dos dados em si não é tão difícil, mas fazer isso com altíssimo desempenho a 60fps é tremendamente difícil. Especialmente ao criar uma API que não vaze e, ao mesmo tempo, não seja trabalhosa demais
Sinceramente, pode ser até mais fácil desenhar pixels em um canvas, como em um jogo, do que transformar mudanças e refleti-las em uma árvore DOM viva
Promises são um bom caso de sucesso, mas, sem async/await, não precisariam necessariamente ter sido padronizadas
O rascunho atual diz se basear em designs de autores/mantenedores de Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz etc., e fico curioso para saber como os autores das bibliotecas existentes veem essa proposta. Também é interessante que React não esteja na lista
Signals são um pouco parecidos com channels, mas diferem por serem broadcast em vez de ter um único receptor. Seria legal se isso permitisse que web workers se comunicassem por channels em vez de callbacks
onMessage. Especialmente se, como em Go, fosse possível fazerselectsobre signals/channels/promises, haveria uma vantagem sintática em relação a gerenciar vários mecanismos de mensageria concorrentes com callbacks. Por exemplo, permitir incluir signals emPromise.anyx instanceof Promisesimplesmente não funciona. Se o métodothenda minha biblioteca aceita um callback de catch e o de outra biblioteca não, elas silenciosamente não interoperam, e não há como detectar isso. Quandofinallyé executado? Que expectativa se pode ter sobre quão assincronamente os callbacks serão executados?Sem um padrão, toda biblioteca que usa Promise precisa trazer seu próprio polyfill, porque não dá para confiar no que já existe. E também não dá para consumir de fato Promises de outras bibliotecas, porque não dá para acreditar que elas se comportem da forma esperada
Isso não é especulação: foi exatamente assim durante anos, e foi um inferno que muita gente teve de suportar
Minha vaga intuição é que signals se parecem demais com um
useEffect()generalizado e, se entrassem no React, tornariam ainda mais confuso o que acontece durante o ciclo de renderização. Para o bem ou para o mal, React escolheu uma forma de atualização diferente de signals. Ainda assim, posso estar errado sobre a aplicabilidadeuseEffectisola de forma limpa o comportamento imperativoIsso parece muito com data binding do Ember e pode acabar virando um pesadelo imperativo. O estado padrão se aproxima de uma “arma apontada para o próprio pé”, e evitar que isso aconteça exige uma enorme carga cognitiva e metapadrões
https://nodejs.org/en/learn/asynchronous-work/the-nodejs-eve...
Não entendi o exemplo no README linkado
// A library or framework defines effects based on other Signal primitivesdeclare function effect(cb: () => void): (() => void);Que biblioteca? Que framework? Foi aí que me perdi. O que é
effect?effect(() => element.innerText = parity.get());Como
effectsabe que deve chamar esse lambda sempre queparitymudar? Ele chama esse lambda toda vez que qualquer signal muda? Se for isso, por que falam de cache? Provavelmente não é issoDe qualquer forma, se entendi corretamente o que os autores queriam transmitir, a ideia de signals em si parece válida. Mas o grande problema desse tipo de arquitetura desacoplada é que, quando a aplicação fica complexa o suficiente, você se perde tentando rastrear por que um evento específico ocorreu. Idealmente, signals deveriam ajustar o stack trace, de modo que, quando um callback fosse chamado, ele já incluísse o stack trace do código que disparou o signal em primeiro lugar
effect, permitindo executar código arbitrário em resposta a atualizações de signals. A introdução a signals e effects na documentação do Preact é boa: https://preactjs.com/guide/v10/signals#effectfnPelo que entendi, esse tipo de função effect primeiro executa o callback uma vez para verificar quais signals foram acessados durante a execução, e chama o callback novamente sempre que um signal do qual ele depende é atualizado. Se o acesso ao signal for síncrono e single-threaded, o simples fato de um signal ter sido acessado durante a execução do callback basta para saber que esse callback deve assinar esse signal
Também dá para fazer isso com getters. A função effect rastreia quais propriedades de um signal foram acessadas no método getter, e, pelo que sei, o Vue 2 usava esse tipo de abordagem no passado. Também é possível rastrear acessos a objetos com proxies. O exemplo da proposta tem um método
getque é chamado para acessar o valor do signal, e a execução desse método permite rastrear as dependências[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
[2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
parity.get()registra uma dependência para a função passada aeffect(). Quandoparityé atualizado, essa função é chamadaEla não é chamada toda vez que um signal muda, mas apenas quando muda um signal do qual ela depende
Nesse caso,
paritydepende deisEven, eisEvendepende decounter. Então, quandocounteré atualizado, toda a cadeia de dependências é invalidada;parityé invalidado e o callback é executado novamenteeffecthipotético, uma leitura cria uma aresta entre o nó de estado do signal e o nó de computação do effect, fazendo na prática com que este último assine escritas futuras do primeiro, para decidir quando reexecutar a computaçãoeffecté uma função arbitrária que você quer chamarEm signals, o mecanismo de rastreamento de dependências sabe quais valores precisam ser recalculados e, como resultado, o sistema também passa a saber quais funções deve chamar novamente
effectRelacionado a isso, existe o S.js: https://github.com/adamhaile/s
Gosto de signals. Ao criar UI, prefiro isso a qualquer outro primitivo, talvez com exceção do algoritmo de restrições Cassowary. Tento imitar signals em todas as linguagens que uso por diversão
Mas não acho de jeito nenhum que isso deva entrar na própria linguagem JavaScript. Gostaria que deixassem a linguagem em paz por um tempo. As pessoas já têm dificuldade de acompanhar, e o TC-39 já está assustando as pessoas e afastando-as da linguagem
Isto parece muito com meu sistema de effects favorito em JS, o MobX
A versão do MobX seria assim
import { observable, computed, autorun } from 'mobx';const counter = observable.box(0);const isEven = computed(() => (counter.get() & 1) === 0);const parity = computed(() => isEven.get() ? "even" : "odd");autorun(() => { element.innerText = parity.get(); });setInterval(() => counter.set(counter.get() + 1), 1000);Passa a sensação de “vamos embutir na biblioteca padrão o framework que estou usando hoje em dia!”
É parecido com tatuar o nome da namorada no corpo
É colocar na biblioteca padrão um componente básico para o qual a maioria dos frameworks convergiu
Promises também entraram na biblioteca padrão depois de serem amplamente usadas, e isto é parecido