«¡Ejecuto una función y devuelve undefined!» — ¿un dolor familiar?
\nImagina: escribes código en JavaScript, haces una solicitud a una API, esperas los datos… y recibes undefined. O peor — Promise {<pending>}. Te desanimas, quieres cerrar el navegador y hacerte monje.
No te apresures. Este problema atormenta incluso a desarrolladores experimentados. No en vano la pregunta «How do I return the response from an asynchronous call?» en Stack Overflow ha acumulado miles de visitas y cientos de respuestas. Hoy lo resolveremos de una vez por todas.
\nEn este artículo aprenderás:
\n- \n
- Por qué
returnno funciona como estás acostumbrado \n - Cómo dominar la asincronía con callbacks, promesas y
async/await\n - Qué enfoque elegir para tu proyecto \n
¡Allá vamos!
\n\n¿Por qué return no devuelve los datos?
\nVamos al grano. Aquí tienes un ejemplo clásico:
\n\nfunction getUserData(userId) {\n fetch(`https://api.example.com/users/${userId}`)\n .then(response => response.json())\n .then(data => {\n return data; // ¿Parece que todo está bien?\n });\n}\n\nconst user = getUserData(42);\nconsole.log(user); // undefined\n\n¿Te resulta familiar? Escribiste return data dentro de .then(), pero afuera obtuviste undefined. ¿Por qué?
JavaScript es de un solo hilo y no bloqueante. Cuando llamas a fetch, no espera la respuesta del servidor. En su lugar, «aplaza» la ejecución del callback y pasa inmediatamente a la siguiente línea. Y la siguiente línea es console.log(user), donde user aún no está definido.
Tu función getUserData termina antes de que llegue la respuesta del servidor. No devuelve nada (devuelve undefined), y los datos quedan «en el aire».
¿Cómo solucionarlo? Hay tres formas principales.
\n\nForma 1: Callbacks — el abuelo de la asincronía
\nAntes, antes de que existieran las promesas, todas las operaciones asíncronas se hacían con callbacks. Pasas una función que se ejecutará cuando lleguen los datos.
\n\nfunction getUserData(userId, callback) {\n fetch(`https://api.example.com/users/${userId}`)\n .then(response => response.json())\n .then(data => {\n callback(data); // Llamamos al callback con los datos\n })\n .catch(error => {\n callback(null, error); // O pasamos el error\n });\n}\n\n// Uso:\ngetUserData(42, (data, error) => {\n if (error) {\n console.error('Error:', error);\n return;\n }\n console.log('Datos del usuario:', data);\n});\n\nVentajas: simplicidad, funciona en todas partes.
\nDesventajas: si anidas varios callbacks, obtienes un «infierno de callbacks» (callback hell). El código se vuelve ilegible.
\n\ngetUserData(42, (data1) => {\n getPosts(data1.id, (data2) => {\n getComments(data2[0].id, (data3) => {\n // ... y así sucesivamente\n });\n });\n});\n\nEl JavaScript moderno se alejó de esto. Pero entender los callbacks es necesario — son la base de todos los demás enfoques.
\n\nForma 2: Promesas — salvadoras de la legibilidad
\nUna promesa es un objeto que representa una «promesa» de devolver un valor en el futuro. Tiene tres estados: pending (pendiente), fulfilled (cumplida) y rejected (rechazada).
Volvamos a nuestro ejemplo. En lugar de un callback, devolvemos la promesa misma:
\n\nfunction getUserData(userId) {\n return fetch(`https://api.example.com/users/${userId}`)\n .then(response => {\n if (!response.ok) {\n throw new Error('La red no está en orden');\n }\n return response.json();\n });\n}\n\n// Uso:\nconst userPromise = getUserData(42);\n\nuserPromise\n .then(data => {\n console.log('Datos del usuario:', data);\n })\n