Executando verificação de segurança...
2

Como o React faz "batching" das atualizações de estado e como promises/async funciona no javascript

Vocês já devem saber que o React por padrão faz um batch quando você faz uma mudança de estado, né?

Sempre fiquei encasquetado de como isso funcionava. A resposta é que ele usa um negócio chamado queueMicrostask do navegador.

Para chegar lá a gente tem que primeiro entender como o javascript funciona.

No javascript nós temos tasks e microtasks. As tasks são, em linhas gerais, um bloco de código. Microtasks são pequenas tarefas que são executadas depois que todas as tasks são executadas.

Mais sobre tasks

São todas colocadas em uma fila e são executadas uma a uma até seu fim.

Quando você define um callback em um botão como addEventListener você está definindo uma task para ser executada em determinado momento pelo navegador.

Como você já deve ter visto antes ou não, o javascript tem esse negócio chamado de event loops. A diferença dele no navegador para ele no node é que no Node ele tem um fim.

Quando seu event loop fica vazio ele simplesmente acaba de rodar o programinha e manda o sinal SIGINT para acabar a execução do programa. No browser ele fica rodando sem parar.

Um exemplo de mais ou menos como seria o funcionamento dele no browser, só que no node é isso aqui:

function keepItRunning() {
  process.nextTick(() => {
    console.log('hey');
    keepItRunning();
  });
}
keepItRunning();

Esse process.nextTick é o que é o usado no quando você roda um servidor express.

import express from 'express';

const app = express()

app.listen(3000, () => console.log('app is running'))

Quando você roda esse script no node, você sabe que o processo não para, né?

Você só consegue parar o processo com Ctrl+C. Tudo o que eu aprendi surgiu dessa duvida na verdade, de como o node mantém o processo ativo.

Uma coisa legal dessa recursão é que ela é completamente válida, se você já tentou fazer recursão no javascript você sabe sabe que ela repete, repete e repete até encher seu Call Stack e ai receber o erro Maximum call stack limit exceeded

O legal é que esse ai que eu mandei, nunca vai chegar em um fim. É tipo um while, mas a diferença dele para um while é a seguinte:

function keepItRunning() {
  process.nextTick(() => {
    console.log('hey');
    keepItRunning();
  });
}
keepItRunning();

console.log('esse bloco de código aqui vai rodar')

Agora no while é assim:

function keepItRunning() {
  while (true) {
     console.log('hey');
  }
}
keepItRunning();

console.log('esse bloco de código aqui NUNCA vai rodar')

Porque isso acontece?

A ideia é que o Event Loop só passa para o próximo TICK (a próxima vez que ele vai verificar se tem algum resultado) quando a execução inteira do seu código termina, do começo ao fim.

No node, quando ele chega no fim, ele vai ver se ainda tem algo pendente pra rodar, e ai chama o nextTick. É por isso que esse é uma recursão válida, porque ao chegar no fim, ele entende que seu programa “terminou”. Ou seja, Call Stack estará “limpa” para a próxima execução.

Ou seja, ele termina uma Task, quando termina, coloca outra task na fila e assim por diante até chegar o fim e não ter mais nada pendente pra resolver.

Beleza, e onde entra o queueMicrotask nisso?

Lembra que eu te falei que no node o Event Loop roda até não ter mais nada pra resolver. E no browser ele fica rodando indefinidamente? Então, no Browser você tem 3 jeitos de “brincar” com esse lance do Event Loop sem ser com promises: useTimeout, requestAnimationFrame e queueMicrotask

O requestAnimationFrame pode ser um tópico por si só. (aliás, se você algum dia tiver que fazer um counter em uma página, escolha o requestAnimationFrame pq ele garante que esse bloco de código (TASK), vai rodar ANTES de atualizar o conteúdo na tela. Ou seja, ele provavelmente nunca vai perder o time.)

Mas voltando ao tópico do queueMicrotask. No browser e no node o que ele faz por padrão é rodar todas suas tasks. Vamo dizer que na minha página eu coloquei 3 tags

Quando isso acabar todas as tasks ele vai verificar um negócio chamado MicroTasks. Essa microtask fica entre a task e o próximo Event Loop Ou seja, ele é a parte final de rodar uma Task.

Outro momento que você está definindo tasks é quando você cria um eventListener com addEventListener(‘click’, callback). Isso esta definindo um novo bloco de código (ou seja uma nova task) que só será adicionada na fila de tasks quando o evento ‘click’ for chamado.

É isso que também se chama o bubbling (aquele lance de e.stopPropagation(), não sei se você já usou), ele vai colocando uma task atrás da outra pra rodar todas em um loop. O e.stopPropagation() é literalmente um break no loop de tasks.

Um exemplo bem simples:

function one() {
    console.log(1)
}

function two() {
    console.log(2)
}


queueMicrotask(() => console.log('teste'))
const main = () => {
  one()
  two()
}
main()

Vamo entender: O que essa task diz? E qual a ordem de execução da task?

1 - Defina a function one

2 - Defina a function two

3 - Joga esse callback pra microtask, em outras palavras, joga isso aqui pro final do código

4 - Defina main

5 - Chame a função main()

6 - Dentro de main chame one()

7 - Dentro de main chame two()

Aqui a nossa task acabou, se isso fosse um script sem queueMicrotask a gente terminaria a execução do script aqui.

No caso usamos o queueMicrotask, então o que fazemos? Jogamos isso lá para o final da execução da task.

Nesse caso o log vai ser respectivamente:

$ 1, 2, ‘teste’

No caso do React, ele faz o mesmo, mas ele engloba uma microtask dentro de outra assim:

async function one() {
    console.log(1)
}

async function two() {
    console.log(2)
}

queueMicrotask(() => queueMicrotask(() => console.log('teste')))
const main = async () => {
  await one()
  await two()
}
main()

Com isso aqui, aquele negócio de automatic batching já funciona.

Ou seja, o que ele ta faz é englobando todos os eventos que ele “manda pra você”. Tipo onClick, onPress, onKeyDown ou o que for, dentro disso. Assim ele consegue ‘salvar suas alterações’ e só “commitar” quando sua task terminar.

NÃO É como se fosse algo tipo assim:

function sendOnClickEventToComponent() {
  callOnClick()
  nowCommit()
}

Você não precisa passar um callback, porque o próprio navegador já vai lidar com isso.

Ou seja, recapitulando, um onClick() no React está fazendo o que? Isso mesmo, criando uma nova task. Essa task vai chamar os callbacks lá que você definiu.

Quando você faz um setCounter(newCounter) e etc ele vai estar definindo esses valores meio que em uma variável global. Assim que sua task acabou, ai ele meio que só faz um this.setState() em todos seus estados e re-renderiza tudo de uma vez.

Beleza, mas qual a diferença disso pro setTimeout()???

O setTimeout joga a execução do seu bloco de código para o próximo event loop, o  queueMicrotas não. Ou seja a ordem de execução seria:

  • Tasks
  • Microtasks
  • Vai pro próximo event loop onde eu vou repetir de novo 1 e 2.

Sendo assim

setTimeout(() => {
    console.log('hey i am executed asychronously by setTimeout');
},0);

queueMicrotask(() => {
    console.log('hey i am executed asychronously by queueMicrotask');
});

Qual vc acha que é executado primeiro? Lembra da ordem execução que eu te falei.

Não é como se queueMicrotask rodasse no próximo event loop, mas é como se ele jogasse um bloco de código pro final. É como se isso:

function one() {
    console.log(1)
}

function two() {
    console.log(2)
}

queueMicrotask(() => console.log('teste'))
const main = () => {
  one()
  two()
}
main()

Fosse:

function one() {
    console.log(1)
}

function two() {
    console.log(2)
}

const main = () => {
  one()
  two()
}
main()

console.log('teste')

Ou seja, se for no node, depois que chegou no fim ali, seu programinha termina e acabou. Depois da ultima linha vc termina esse loop do event loop e vai para o próximo.

Se não tiver nada, a execução do seu programa acabou ai. Aqui vc entende como process.nextTick e queueMicrotask estão relacionados:

https://www.freecodecamp.org/news/queuemicrotask/

Outras referências disso tudo:

Carregando publicação patrocinada...
2

Perfeitamente, uma ajuda para voce que não entendeu alguns palavas a cima. Apenas um complemento meu caro @nicolasmelo1

batch- Um arquivo batch é um arquivo texto contendo linhas com comandos que podem ser executados sequencialmente pelo interpretador de comandos do MS-DOS, Windows ou OS/2. São identificados pelas extensões .bat ou .cmd.

queueMicrotask - The queueMicrotask() method, which is exposed on the Window or Worker interface, queues a microtask to be executed at a safe time prior to control returning to the browser's event loop.

nextTick - O loop de eventos é o que permite que o Node.js execute operações de E/S sem bloqueio — apesar do fato de o JavaScript ser de thread único — descarregando as operações para o kernel do sistema sempre que possível.

1