JavaScript Avançado

Guia Completo de Promises Internamente: Implementando uma Promise do Zero

8 min de leitura

Guia Completo de Promises Internamente: Implementando uma Promise do Zero

O Que é uma Promise e Por Que Implementar do Zero Uma Promise é um objeto JavaScript que representa a eventual conclusão (ou falha) de uma operação assíncrona e seu valor resultante. Aprender a implementar uma Promise internamente é fundamental para entender como JavaScript gerencia operações assíncronas. Quando você implementa do zero, compreende os estados (pending, fulfilled, rejected), o fluxo de callbacks e por que o padrão é tão poderoso para evitar callback hell. Nesta aula, construiremos uma Promise funcional, respeitando a especificação Promises/A+. Você verá que uma Promise não é "mágica" — é um padrão bem estruturado que você pode reproduzir. Após esta jornada, entenderá por que , e funcionam como funcionam, e poderá até debugar Promises com confiança. Arquitetura Interna: Estados e Transições Os Três Estados de uma Promise Uma Promise começa em estado pending (pendente). Durante a execução do executor (a função passada ao construtor), ela pode fazer uma transição irreversível para fulfilled (cumprida, com um valor)

<h2>O Que é uma Promise e Por Que Implementar do Zero</h2>

<p>Uma Promise é um objeto JavaScript que representa a eventual conclusão (ou falha) de uma operação assíncrona e seu valor resultante. Aprender a implementar uma Promise internamente é fundamental para entender como JavaScript gerencia operações assíncronas. Quando você implementa do zero, compreende os estados (pending, fulfilled, rejected), o fluxo de callbacks e por que o padrão é tão poderoso para evitar callback hell.</p>

<p>Nesta aula, construiremos uma Promise funcional, respeitando a especificação Promises/A+. Você verá que uma Promise não é &quot;mágica&quot; — é um padrão bem estruturado que você pode reproduzir. Após esta jornada, entenderá por que <code>.then()</code>, <code>.catch()</code> e <code>.finally()</code> funcionam como funcionam, e poderá até debugar Promises com confiança.</p>

<h2>Arquitetura Interna: Estados e Transições</h2>

<h3>Os Três Estados de uma Promise</h3>

<p>Uma Promise começa em estado <strong>pending</strong> (pendente). Durante a execução do executor (a função passada ao construtor), ela pode fazer uma transição irreversível para <strong>fulfilled</strong> (cumprida, com um valor) ou <strong>rejected</strong> (rejeitada, com uma razão/erro).</p>

<pre><code class="language-javascript">class MinhaPromise {

constructor(executor) {

this.state = &#039;pending&#039;; // pending | fulfilled | rejected

this.value = undefined;

this.reason = undefined;

this.onFulfilledCallbacks = [];

this.onRejectedCallbacks = [];

const resolve = (value) =&gt; {

if (this.state === &#039;pending&#039;) {

this.state = &#039;fulfilled&#039;;

this.value = value;

this.onFulfilledCallbacks.forEach(cb =&gt; cb(value));

}

};

const reject = (reason) =&gt; {

if (this.state === &#039;pending&#039;) {

this.state = &#039;rejected&#039;;

this.reason = reason;

this.onRejectedCallbacks.forEach(cb =&gt; cb(reason));

}

};

try {

executor(resolve, reject);

} catch (error) {

reject(error);

}

}

}

// Uso

new MinhaPromise((resolve, reject) =&gt; {

setTimeout(() =&gt; resolve(&#039;Sucesso!&#039;), 1000);

});</code></pre>

<p>O executor é chamado <strong>imediatamente</strong> com as funções <code>resolve</code> e <code>reject</code>. A transição de estado só ocorre uma vez — chamadas subsequentes são ignoradas. Os callbacks são armazenados para execução quando a Promise se resolver.</p>

<h2>Implementando .then(), .catch() e Encadeamento</h2>

<h3>O Método then() e Chain-ability</h3>

<p>O <code>.then()</code> retorna uma <strong>nova Promise</strong>, não a original. Isso permite encadeamento. A novidade aqui é a resolução de Promises intermediárias — se um callback retorna outra Promise, a Promise filha aguarda sua conclusão.</p>

<pre><code class="language-javascript">class MinhaPromise {

constructor(executor) {

this.state = &#039;pending&#039;;

this.value = undefined;

this.reason = undefined;

this.onFulfilledCallbacks = [];

this.onRejectedCallbacks = [];

const resolve = (value) =&gt; {

if (this.state !== &#039;pending&#039;) return;

this.state = &#039;fulfilled&#039;;

this.value = value;

this.onFulfilledCallbacks.forEach(cb =&gt; cb());

};

const reject = (reason) =&gt; {

if (this.state !== &#039;pending&#039;) return;

this.state = &#039;rejected&#039;;

this.reason = reason;

this.onRejectedCallbacks.forEach(cb =&gt; cb());

};

try {

executor(resolve, reject);

} catch (error) {

reject(error);

}

}

then(onFulfilled, onRejected) {

// Garantir que são funções

onFulfilled = typeof onFulfilled === &#039;function&#039; ? onFulfilled : x =&gt; x;

onRejected = typeof onRejected === &#039;function&#039; ? onRejected : err =&gt; { throw err; };

return new MinhaPromise((resolve, reject) =&gt; {

const handleFulfilled = () =&gt; {

try {

const result = onFulfilled(this.value);

resolvePromise(this, result, resolve, reject);

} catch (error) {

reject(error);

}

};

const handleRejected = () =&gt; {

try {

const result = onRejected(this.reason);

resolvePromise(this, result, resolve, reject);

} catch (error) {

reject(error);

}

};

if (this.state === &#039;fulfilled&#039;) {

setTimeout(handleFulfilled, 0);

} else if (this.state === &#039;rejected&#039;) {

setTimeout(handleRejected, 0);

} else {

this.onFulfilledCallbacks.push(handleFulfilled);

this.onRejectedCallbacks.push(handleRejected);

}

});

}

catch(onRejected) {

return this.then(null, onRejected);

}

finally(onFinally) {

return this.then(

value =&gt; MinhaPromise.resolve(onFinally()).then(() =&gt; value),

reason =&gt; MinhaPromise.resolve(onFinally()).then(() =&gt; { throw reason; })

);

}

static resolve(value) {

if (value instanceof MinhaPromise) return value;

return new MinhaPromise(resolve =&gt; resolve(value));

}

static reject(reason) {

return new MinhaPromise((_, reject) =&gt; reject(reason));

}

}

function resolvePromise(promise, x, resolve, reject) {

if (x === promise) {

reject(new TypeError(&#039;Circular reference&#039;));

return;

}

if (x !== null &amp;&amp; (typeof x === &#039;object&#039; || typeof x === &#039;function&#039;)) {

try {

const then = x.then;

if (typeof then === &#039;function&#039;) {

then.call(

x,

y =&gt; resolvePromise(promise, y, resolve, reject),

r =&gt; reject(r)

);

return;

}

} catch (error) {

reject(error);

return;

}

}

resolve(x);

}

// Teste

new MinhaPromise((resolve) =&gt; {

resolve(5);

})

.then(value =&gt; {

console.log(value); // 5

return value * 2;

})

.then(value =&gt; {

console.log(value); // 10

return new MinhaPromise(res =&gt; res(value + 10));

})

.then(value =&gt; console.log(value)) // 20

.catch(err =&gt; console.error(err));</code></pre>

<h3>Mecanismo de Resolução (Thenable)</h3>

<p>A função <code>resolvePromise</code> é crítica: ela detecta quando um callback retorna um objeto &quot;thenable&quot; (que possui <code>.then()</code>) e aguarda sua resolução. Isso permite composição de Promises e é o coração do encadeamento. A função usa <code>setTimeout</code> para garantir execução assíncrona, evitando bloqueios.</p>

<h2>Tratamento de Erros e Casos Especiais</h2>

<h3>Captura de Exceções e Propagação</h3>

<p>Erros lançados em executores são automaticamente convertidos em rejeições. Quando um callback lança erro, a Promise resultante é rejeitada. O <code>.catch()</code> é apenas sintaxe para <code>.then(null, handler)</code>.</p>

<pre><code class="language-javascript">// Erro no executor

new MinhaPromise((resolve, reject) =&gt; {

throw new Error(&#039;Boom!&#039;);

})

.catch(err =&gt; console.error(&#039;Capturado:&#039;, err.message));

// Erro em callback

MinhaPromise.resolve(10)

.then(value =&gt; {

throw new Error(&#039;Erro no then&#039;);

})

.catch(err =&gt; console.log(&#039;Tratado:&#039;, err.message));

// Propagação de erro

MinhaPromise.reject(&#039;Motivo&#039;)

.then(x =&gt; x * 2) // Pulado

.then(x =&gt; x + 5) // Pulado

.catch(reason =&gt; console.log(&#039;Final:&#039;, reason)); // &#039;Motivo&#039;</code></pre>

<p>A beleza desta arquitetura é que erros propagam automaticamente pela chain até encontrar um <code>.catch()</code>. Sem tratamento, o erro é &quot;silenciosamente perdido&quot; em Promises nativas — implementações reais têm mecanismos para avisar sobre rejeições não tratadas.</p>

<h2>Conclusão</h2>

<p><strong>Três aprendizados principais:</strong> (1) Uma Promise é um gerenciador de estado que transiciona entre pendente, cumprido e rejeitado, executando callbacks registrados no tempo certo; (2) O <code>.then()</code> retorna sempre uma nova Promise, permitindo encadeamento transparente através do mecanismo de resolução de Promises intermediárias; (3) Erros são parte integral do fluxo — tratados como rejeições que propagam pela corrente até serem capturados ou descartados.</p>

<p>Implementar do zero revela que Promises não contêm &quot;magia assíncrona&quot; — elas organizam callbacks de forma previsível. Agora você pode ler código assíncrono com confiança, entender freezes e debugar problemas de timing. Na próxima vez que usar <code>async/await</code> (que é açúcar sintático sobre Promises), saberá exatamente o que está acontecendo internamente.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://promisesaplus.com/" target="_blank" rel="noopener noreferrer">Promises/A+ Specification</a> — Especificação oficial que padroniza o comportamento de Promises</li>

<li><a href="https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" rel="noopener noreferrer">MDN Web Docs - Promise</a> — Documentação oficial da Mozilla</li>

<li><a href="https://github.com/getify/You-Dont-Know-JS/tree/2nd-ed/async-performance" target="_blank" rel="noopener noreferrer">You Don&#039;t Know JS: Async &amp; Performance</a> — Livro de Kyle Simpson sobre assincronismo</li>

<li><a href="https://javascript.info/promise-basics" target="_blank" rel="noopener noreferrer">JavaScript.info - Promise</a> — Tutorial interativo com explicações profundas</li>

<li><a href="https://exploringjs.com/es6/ch_promises.html" target="_blank" rel="noopener noreferrer">Exploring ES6 - Promises</a> — Capítulo do livro de Axel Rauschmayer sobre ES6</li>

</ul>

Comentários

Mais em JavaScript Avançado

Prototype Chain Avançado: Object.create, getPrototypeOf e Herança Real: Do Básico ao Avançado
Prototype Chain Avançado: Object.create, getPrototypeOf e Herança Real: Do Básico ao Avançado

Object.create: A Base da Herança Real é o método fundamental para criar heran...

Como Usar Event Loop Avançado: Microtasks, Macrotasks e requestAnimationFrame em Produção
Como Usar Event Loop Avançado: Microtasks, Macrotasks e requestAnimationFrame em Produção

O que é Event Loop e sua Estrutura Fundamental O Event Loop é o mecanismo cen...

Programação Funcional em JavaScript: Imutabilidade, Pureza e Composição: Do Básico ao Avançado
Programação Funcional em JavaScript: Imutabilidade, Pureza e Composição: Do Básico ao Avançado

Imutabilidade: O Fundamento da Programação Funcional A imutabilidade é o pila...