Creación de Promesas en JavaScript: Fundamentos del Constructor Promise
Aprende a crear promesas desde cero usando el constructor Promise y entiende los estados de resuelto y rechazado.
TL;DR - Resumen rápido
- Una promesa es un objeto que representa un valor eventual
- Las promesas tienen tres estados: pending, fulfilled y rejected
- El constructor Promise recibe una función executor con resolve y reject
- resolve() marca la promesa como cumplida con un valor
- reject() marca la promesa como fallida con un motivo
Introducción a las Promesas
Las promesas (Promises) son una de las características más importantes de ES6 y la base de la programación asíncrona moderna en JavaScript. Una promesa es un objeto que representa la eventual finalización (o falla) de una operación asíncrona, permitiéndote encadenar operaciones asíncronas de manera más elegante que con los callbacks tradicionales.
Antes de las promesas (introducidas en ES6/ES2015), los desarrolladores dependían exclusivamente de callbacks para operaciones asíncronas, lo que resultaba en el "Callback Hell". Las promesas resolvieron esto proporcionando un objeto con métodos encadenables (then, catch, finally), convirtiendo código anidado en secuencias planas y legibles.
¿Por qué son importantes?
Las promesas son la base de async/await, la sintaxis más moderna y legible para código asíncrono. Entender cómo crear promesas manualmente es esencial para dominar async/await y para entender cómo funcionan las promesas bajo el capó.
¿Qué es una Promesa?
Una promesa es un objeto que representa la eventual finalización o falla de una operación asíncrona. A diferencia de un callback, que es solo una función, una promesa es un objeto con métodos como then(), catch() yfinally() que te permiten manejar el resultado o el error de la operación.
Estados de una Promesa
Una promesa puede estar en uno de tres estados: pending (pendiente), fulfilled (cumplida) o rejected (rechazada). Cuando creas una promesa, está en estado pending. Luego, puede transicionar a fulfilled si la operación tiene éxito, o a rejected si la operación falla. Una vez que una promesa está en fulfilled o rejected, no puede cambiar de estado.
Este ejemplo muestra los tres estados de una promesa. La promesa comienza en estado pending, y luego transiciona a fulfilled o rejected dependiendo de si la operación tiene éxito o falla. Una vez que la promesa está en fulfilled o rejected, su valor final es inmutable y no puede cambiar.
- <strong>Pending</strong>: La promesa está pendiente, aún no se ha resuelto ni rechazado
- <strong>Fulfilled</strong>: La promesa se cumplió exitosamente con un valor
- <strong>Rejected</strong>: La promesa falló con un motivo o error
- <strong>Settled</strong>: La promesa está en fulfilled o rejected (ya no está pending)
- <strong>Inmutable</strong>: Una vez settled, el estado y valor de la promesa no pueden cambiar
Constructor Promise
El constructor Promise crea una nueva promesa recibiendo una función executor que tiene dos parámetros: resolve y reject. El executor se ejecuta **síncronamente e inmediatamente** cuando se crea la promesa (antes de que el constructor retorne). Dentro del executor, llama a resolve(valor)para cumplir la promesa, o reject(error) para rechazarla. Ambas funciones son asíncronas: programan la ejecución de callbacks en la Microtask Queue.
Ejecución Síncrona del Executor
El executor se ejecuta síncronamente cuando creas la promesa, pero resolve()y reject() son asíncronos: agregan callbacks a la Microtask Queue. Esto significa que el código después de new Promise() se ejecuta antes que los callbacks de then() o catch(), incluso si llamas aresolve() inmediatamente.
Crear una Promesa Resuelta
Para crear una promesa que se resuelve exitosamente, llamas a resolve()dentro del executor, pasando el valor que quieres que la promesa retorne. Este valor estará disponible en el método then() de la promesa. Puedes resolver una promesa con cualquier valor: primitivos, objetos, arrays o incluso otras promesas.
Este ejemplo muestra cómo crear una promesa que se resuelve exitosamente. La funciónobtenerUsuario crea una promesa que se resuelve después de 1 segundo con un objeto usuario. El valor que pasamos a resolve() estará disponible en el callback de then().
Crear una Promesa Rechazada
Para crear una promesa que falla, llamas a reject() dentro del executor, pasando el motivo del rechazo (generalmente un objeto Error). Este error estará disponible en el método catch() de la promesa. Es una buena práctica siempre rechazar promesas con objetos Error para tener stack traces útiles al depurar.
Este ejemplo muestra cómo crear una promesa que se rechaza. La funciónobtenerUsuarioFallido crea una promesa que se rechaza después de 1 segundo con un objeto Error. El error que pasamos a reject() estará disponible en el callback de catch().
throw vs reject en el Executor
Si lanzas un error con throw dentro del executor, JavaScript automáticamente convierte la promesa a rejected con ese error. Es equivalente a llamarreject(error). Sin embargo, throw en un setTimeoutdentro del executor NO rechaza la promesa, porque el error ocurre fuera del contexto del executor. Usa reject() explícitamente en callbacks asíncronos.
Promesas Ya Resueltas
A veces ya tienes el valor disponible inmediatamente pero quieres mantener la consistencia de tu API retornando siempre promesas. Promise.resolve(valor) crea una promesa ya cumplida, y Promise.reject(error) crea una promesa ya rechazada. Aunque están "resueltas" inmediatamente, sus callbacks then()/catch()se ejecutan de forma asíncrona en la Microtask Queue, no síncronamente.
Este ejemplo muestra cómo usar Promise.resolve() y Promise.reject()para crear promesas ya resueltas o rechazadas. El caso de uso común es cuando tienes datos en caché: puedes retornar Promise.resolve(datosCache) inmediatamente en lugar de hacer una petición asíncrona. Esto mantiene la consistencia de tu API (siempre retornas promesas) pero optimiza el rendimiento cuando los datos ya están disponibles.
Errores Comunes al Crear Promesas
Los siguientes errores son comunes al crear promesas manualmente. Estos bugs pueden causar que tu código se "congele" esperando promesas que nunca se resuelven, o comportarse de forma impredecible cuando múltiples resoluciones compiten.
Error 1: No Llamar a resolve o reject
Crear una promesa sin llamar a resolve() o reject() causa que quede en estado pending permanentemente. Esto es especialmente común cuando olvidas llamarlos dentro de callbacks asíncronos (como setTimeout o event listeners). El resultado es que los callbacks de then()/catch() nunca se ejecutan, causando que tu aplicación se "congele" esperando una respuesta que nunca llega.
Este ejemplo muestra el problema: el executor crea el usuario pero olvida llamar aresolve(), dejando la promesa en pending permanentemente. El callback dethen() nunca se ejecuta. Este error es común cuando migras código de callbacks a promesas y olvidas agregar resolve() al final de la lógica asíncrona.
Error 2: Llamar a resolve o reject Múltiples Veces
Llamar a resolve() o reject() múltiples veces es un error común en código con múltiples ramas condicionales o callbacks anidados. JavaScript ignora silenciosamente las llamadas después de la primera: una vez que la promesa está settled (fulfilled o rejected), es inmutable. Esto puede causar bugs sutiles donde código parece correcto pero el segundo resolve() es ignorado.
Este ejemplo demuestra que solo el primer resolve() tiene efecto, los subsiguientes son ignorados silenciosamente. El callback de then() solo recibe el primer valor (Juan). Si necesitas múltiples valores, debes crear múltiples promesas o usar un observable. La inmutabilidad de promesas settled es por diseño: garantiza que el valor de una promesa nunca cambia después de resolverse.
Advertencia sobre Promesas Pendientes
Las promesas pendientes que nunca se resuelven causan memory leaks. Si tienes código esperando con await o then() a una promesa que nunca se resuelve, ese código se congela permanentemente y los recursos no se liberan. En Node.js, esto puede acumularse hasta agotar la memoria. Siempre asegura que tus promesas eventualmente se resuelvan o rechacen, incluso en casos de error.
Resumen: Creación de Promesas
Conceptos principales:
- •Una promesa es un objeto que representa un valor eventual
- •Las promesas tienen tres estados: pending, fulfilled y rejected
- •El constructor Promise recibe un executor con resolve y reject
- •resolve() marca la promesa como cumplida con un valor
- •reject() marca la promesa como fallida con un motivo
Mejores prácticas:
- •Siempre rechaza promesas con objetos Error para tener stack traces
- •Llama a resolve() o reject() exactamente una vez por promesa
- •Usa Promise.resolve() para valores ya disponibles inmediatamente
- •Usa Promise.reject() para rechazos inmediatos
- •Asegúrate de que la promesa siempre se resuelva o rechace