JavaScript Avançado

Concorrência Real em JavaScript: Coordenando Múltiplas Promises na Prática

8 min de leitura

Concorrência Real em JavaScript: Coordenando Múltiplas Promises na Prática

O Que é Concorrência Real em JavaScript? Diferente da concorrência "falsa" criada pelo event loop, concorrência real envolve executar múltiplas operações assíncronas em paralelo, coordenando seus resultados de forma eficiente. Em JavaScript, isso é realizado através de Promises, que permitem trabalhar com operações não-bloqueantes como requisições HTTP, leitura de arquivos e acesso a bancos de dados. Uma Promise representa um valor que pode estar disponível agora, no futuro ou nunca. O grande desafio é coordenar várias Promises simultaneamente, esperando que todas sejam resolvidas ou tratando falhas parciais. Para dominar concorrência real, você precisa entender quando usar , , e — cada uma com seu caso de uso específico. Coordenando Múltiplas Promises com Métodos Nativos Promise.all() — Tudo ou Nada Use quando todos os resultados são críticos. Se uma Promise rejeita, toda a operação falha imediatamente: Promise.allSettled() — Resultados Parciais Aceitáveis Quando você precisa dos resultados de todas as Promises, mas algumas podem falhar: Arquivo ${index + 1}: Sucesso - Arquivo

<h2>O Que é Concorrência Real em JavaScript?</h2>

<p>Diferente da concorrência &quot;falsa&quot; criada pelo event loop, concorrência real envolve executar múltiplas operações assíncronas <strong>em paralelo</strong>, coordenando seus resultados de forma eficiente. Em JavaScript, isso é realizado através de Promises, que permitem trabalhar com operações não-bloqueantes como requisições HTTP, leitura de arquivos e acesso a bancos de dados.</p>

<p>Uma Promise representa um valor que pode estar disponível agora, no futuro ou nunca. O grande desafio é coordenar várias Promises simultaneamente, esperando que todas sejam resolvidas ou tratando falhas parciais. Para dominar concorrência real, você precisa entender quando usar <code>Promise.all()</code>, <code>Promise.race()</code>, <code>Promise.allSettled()</code> e <code>Promise.any()</code> — cada uma com seu caso de uso específico.</p>

<h2>Coordenando Múltiplas Promises com Métodos Nativos</h2>

<h3>Promise.all() — Tudo ou Nada</h3>

<p>Use <code>Promise.all()</code> quando <strong>todos</strong> os resultados são críticos. Se uma Promise rejeita, toda a operação falha imediatamente:</p>

<pre><code class="language-javascript">const fetchUserData = async () =&gt; {

const urls = [

&#039;https://api.github.com/users/torvalds&#039;,

&#039;https://api.github.com/users/gvanrossum&#039;,

&#039;https://api.github.com/users/brendaneich&#039;

];

try {

const responses = await Promise.all(urls.map(url =&gt; fetch(url)));

const data = await Promise.all(responses.map(r =&gt; r.json()));

console.log(&#039;Todos os dados carregados:&#039;, data);

} catch (error) {

console.error(&#039;Erro ao buscar dados:&#039;, error.message);

}

};

fetchUserData();</code></pre>

<h3>Promise.allSettled() — Resultados Parciais Aceitáveis</h3>

<p>Quando você precisa dos resultados de <strong>todas</strong> as Promises, mas algumas podem falhar:</p>

<pre><code class="language-javascript">const processMultipleFiles = async () =&gt; {

const filePromises = [

fetch(&#039;/api/file1&#039;).then(r =&gt; r.json()),

fetch(&#039;/api/file2&#039;).then(r =&gt; r.json()),

fetch(&#039;/api/file3&#039;).then(r =&gt; r.json())

];

const results = await Promise.allSettled(filePromises);

results.forEach((result, index) =&gt; {

if (result.status === &#039;fulfilled&#039;) {

console.log(Arquivo ${index + 1}: Sucesso -, result.value);

} else {

console.log(Arquivo ${index + 1}: Erro -, result.reason.message);

}

});

};

processMultipleFiles();</code></pre>

<h3>Promise.race() e Promise.any()</h3>

<p><code>Promise.race()</code> retorna assim que <strong>a primeira</strong> Promise é resolvida ou rejeitada — útil para implementar timeouts. <code>Promise.any()</code> retorna a <strong>primeira Promise que resolve com sucesso</strong>, ignorando rejeições até que todas falhem:</p>

<pre><code class="language-javascript">// Implementando timeout com race()

const fetchWithTimeout = (url, timeoutMs) =&gt; {

return Promise.race([

fetch(url),

new Promise((_, reject) =&gt;

setTimeout(() =&gt; reject(new Error(&#039;Timeout&#039;)), timeoutMs)

)

]);

};

// Promise.any() — ignore falhas parciais

const tryMultipleServers = async () =&gt; {

const servers = [

fetch(&#039;https://server1.com/api&#039;),

fetch(&#039;https://server2.com/api&#039;),

fetch(&#039;https://server3.com/api&#039;)

];

try {

const firstSuccess = await Promise.any(servers);

const data = await firstSuccess.json();

console.log(&#039;Primeiro servidor respondeu:&#039;, data);

} catch (error) {

console.error(&#039;Todos os servidores falharam&#039;);

}

};

tryMultipleServers();</code></pre>

<h2>Padrões Avançados: Controle Fino da Concorrência</h2>

<h3>Executar Promises Sequencialmente com Dependências</h3>

<p>Nem sempre você quer paralelismo total. Quando uma operação depende do resultado da anterior, use <code>async/await</code> com loops:</p>

<pre><code class="language-javascript">const fetchRelatedData = async () =&gt; {

try {

// Busca usuário

const userResponse = await fetch(&#039;/api/user/123&#039;);

const user = await userResponse.json();

console.log(&#039;Usuário:&#039;, user.name);

// Busca posts do usuário (depende do ID)

const postsResponse = await fetch(/api/users/${user.id}/posts);

const posts = await postsResponse.json();

console.log(&#039;Posts:&#039;, posts.length);

// Busca comentários (depende dos IDs dos posts)

const comments = await Promise.all(

posts.map(post =&gt; fetch(/api/posts/${post.id}/comments).then(r =&gt; r.json()))

);

console.log(&#039;Total de comentários:&#039;, comments.flat().length);

} catch (error) {

console.error(&#039;Erro na cadeia de requisições:&#039;, error);

}

};

fetchRelatedData();</code></pre>

<h3>Pool de Concorrência — Limitar Paralelismo</h3>

<p>Para não sobrecarregar a aplicação, controle quantas Promises rodam <strong>simultaneamente</strong>:</p>

<pre><code class="language-javascript">const concurrencyPool = async (promises, poolSize) =&gt; {

const results = [];

const executing = [];

for (let promise of promises) {

const exec = Promise.resolve(promise).then(

(result) =&gt; {

executing.splice(executing.indexOf(exec), 1);

return result;

}

);

results.push(exec);

executing.push(exec);

if (executing.length &gt;= poolSize) {

await Promise.race(executing);

}

}

return Promise.all(results);

};

// Uso: processar 100 requisições com máximo de 5 simultâneas

const urls = Array.from({ length: 100 }, (_, i) =&gt; https://api.example.com/item/${i});

const promises = urls.map(url =&gt; fetch(url).then(r =&gt; r.json()));

concurrencyPool(promises, 5).then(results =&gt; {

console.log(&#039;Todas as requisições completadas:&#039;, results.length);

});</code></pre>

<h2>Tratamento de Erros e Debugging</h2>

<p>Erros em Promises precisam de cuidado especial em concorrência. Use <code>.catch()</code> granular ou <code>try/catch</code> com <code>async/await</code>:</p>

<pre><code class="language-javascript">const robustFetching = async () =&gt; {

const tasks = [

fetch(&#039;/api/critical&#039;).catch(e =&gt; {

console.error(&#039;Critical endpoint falhou:&#039;, e);

throw e; // Re-lança para Promise.all detectar

}),

fetch(&#039;/api/optional&#039;).catch(e =&gt; {

console.warn(&#039;Optional endpoint falhou, continuando com valor padrão&#039;);

return { data: null }; // Recuperação graciosa

})

];

try {

const [critical, optional] = await Promise.all(tasks);

const criticalData = await critical.json();

const optionalData = await optional.json();

console.log(&#039;Processamento bem-sucedido:&#039;, { criticalData, optionalData });

} catch (error) {

console.error(&#039;Falha fatal na operação concorrente:&#039;, error);

}

};

robustFetching();</code></pre>

<h2>Conclusão</h2>

<p>Dominar concorrência em JavaScript significa escolher a ferramenta certa para cada cenário: use <code>Promise.all()</code> quando todos os resultados são essenciais, <code>Promise.allSettled()</code> para tolerância a falhas, <code>Promise.race()</code> para race conditions e <code>Promise.any()</code> para redundância. Implemente pools de concorrência para não sobrecarregar recursos e sempre trate erros com precisão. A combinação de <code>async/await</code> com esses métodos nativos oferece controle fino sobre operações paralelas, transformando código assíncrono complexo em lógica clara e performática.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all" target="_blank" rel="noopener noreferrer">MDN Web Docs - Promise.all()</a></li>

<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function" target="_blank" rel="noopener noreferrer">MDN Web Docs - async/await</a></li>

<li><a href="https://javascript.info/promise" target="_blank" rel="noopener noreferrer">JavaScript.info - Promises</a></li>

<li><a href="https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/" target="_blank" rel="noopener noreferrer">Node.js Documentation - Asynchronous Operations</a></li>

<li><a href="https://eloquentjavascript.net/11_async.html" target="_blank" rel="noopener noreferrer">Eloquent JavaScript - Promises</a></li>

</ul>

Comentários

Mais em JavaScript Avançado

Guia Completo de TypeScript para Bibliotecas: Escrevendo .d.ts e Tipos Públicos
Guia Completo de TypeScript para Bibliotecas: Escrevendo .d.ts e Tipos Públicos

Por que TypeScript é Essencial para Bibliotecas Quando você cria uma bibliote...

Guia Completo de Promises Internamente: Implementando uma Promise do Zero
Guia Completo de Promises Internamente: Implementando uma Promise do Zero

O Que é uma Promise e Por Que Implementar do Zero Uma Promise é um objeto Jav...

Monitoramento de Apps Node.js em Produção: OpenTelemetry e Sentry: Do Básico ao Avançado
Monitoramento de Apps Node.js em Produção: OpenTelemetry e Sentry: Do Básico ao Avançado

Introdução: Por que monitorar Node.js em produção? Aplicações Node.js em prod...