Uma thread para governar todas: mitos e verdades sobre concorrência em JavaScript
\n\nHá muito tempo, na era dos sites estáticos e botões "Enviar" que bloqueavam toda a interface, o JavaScript vivia feliz em uma única thread. Era suficiente: a requisição chegava, era executada, a requisição ia embora, a interface congelava. Mas os tempos mudaram. Hoje, esperamos que as aplicações web reajam instantaneamente: a rolagem não deve travar, as animações devem ser suaves e o carregamento de dados, imperceptível. E é aí que a concorrência entra em jogo.
\n\nMuitos desenvolvedores confundem concorrência (concurrency) com paralelismo (parallelism). Em JavaScript, especialmente no navegador, lidamos mais com o primeiro. É como um malabarista: ele não segura todas as bolas no ar ao mesmo tempo (isso é paralelismo), mas alterna entre elas com tanta habilidade que cria a ilusão de simultaneidade. Neste artigo, vamos explorar três ferramentas-chave para organizar a concorrência em JS: async/await, goroutines (através de geradores e Web Workers) e threads clássicas. Vamos nessa!
Async/Await: código assíncrono elegante
\n\nasync/await é um açúcar sintático sobre Promises, que surgiu no ES2017. Ele não torna o código multi-thread, mas o torna assíncrono. Quando você escreve await, o JavaScript "adormece" nessa função, liberando a thread para outras tarefas. Assim que a Promise é resolvida, a função "acorda" e continua a execução. É a ferramenta ideal para operações de I/O: requisições a APIs, leitura de arquivos, timers.
Exemplo simples: carregando dados do servidor
\n\n// Função que retorna uma Promise\nfunction fetchUser(id) {\n return new Promise((resolve) => {\n setTimeout(() => resolve({ id, name: 'Alexei' }), 1000);\n });\n}\n\n// Função assíncrona\nasync function loadUserProfile(userId) {\n console.log('Iniciando carregamento...');\n const user = await fetchUser(userId);\n console.log('Usuário carregado:', user.name);\n return user;\n}\n\nloadUserProfile(42);\nconsole.log('Este código será executado imediatamente, sem esperar o carregamento!');\n\n\nObserve: console.log('Este código será executado imediatamente...') será executado antes de console.log('Usuário carregado'). A thread não é bloqueada — isso é concorrência em ação.
Tratamento de erros
\n\nasync function fetchWithRetry(url, retries = 3) {\n for (let i = 0; i < retries; i++) {\n try {\n const response = await fetch(url);\n if (!response.ok) throw new Error('Erro HTTP');\n return await response.json();\n } catch (error) {\n if (i === retries - 1) throw error;\n console.log(`Tentativa ${i + 1} falhou, tentando novamente...`);\n }\n }\n}\n\n\nQuando usar: qualquer operação com recursos externos (API, bancos de dados, sistema de arquivos no Node.js). async/await é sua principal ferramenta para código assíncrono.
Goroutines em JavaScript: mito ou realidade?
\n\nO termo "goroutine" veio do Go e se refere a uma thread de execução leve. Em JavaScript, não há goroutines nativas, mas existem geradores (Generator Functions) e Web Workers, que podem simular um comportamento semelhante. Geradores permitem pausar e retomar a execução de uma função, dando controle sobre o fluxo de execução.
\n\nGeradores como base da concorrência
\n\nfunction* taskGenerator() {\n console.log('Iniciando tarefa 1');\n yield 'Tarefa 1 concluída';\n console.log('Iniciando tarefa 2');\n yield 'Tarefa 2 concluída';\n console.log('Iniciando tarefa 3');\n yield 'Tarefa 3 concluída';\n}\n\nconst iterator = taskGenerator();\nconsole.log(iterator.next().value); // Executa tarefa 1\n// Neste momento, podemos fazer outra coisa\nconsole.log(iterator.next().value); // Executa tarefa 2\nconsol