Ir al contenido principal

Return y valores devueltos

Introducción

En el artículo anterior exploramos cómo las funciones pueden recibir información a través de parámetros. Ahora veremos la otra cara de la moneda: cómo las funciones pueden devolver valores utilizando la sentencia return. La capacidad de retornar valores es lo que permite a las funciones no solo procesar datos sino también producir resultados que podemos utilizar en otras partes de nuestro código. Esta característica es fundamental para crear componentes de software modulares y reutilizables. En este artículo, entenderemos el propósito y funcionamiento de return, los diferentes tipos de datos que pueden devolverse y las mejores prácticas para trabajar con valores devueltos.

Propósito de la sentencia return

La sentencia return tiene dos funciones principales:

  1. Devolver un valor: Permite que la función entregue un resultado que puede ser utilizado donde se invocó la función.
  2. Finalizar la ejecución: Cuando JavaScript encuentra un return, detiene inmediatamente la ejecución de la función y devuelve el control al punto donde fue llamada.

Veamos un ejemplo sencillo:

function sumar(a, b) {
  return a + b; // Devuelve la suma y finaliza la función
  console.log("Este código nunca se ejecutará");
}

let resultado = sumar(5, 3);
console.log(resultado); // 8

En este ejemplo, sumar() devuelve el valor de a + b, que luego se asigna a la variable resultado. Es importante notar que cualquier código que aparezca después de un return nunca se ejecutará.

Devolución de diferentes tipos de datos

Las funciones en JavaScript pueden devolver cualquier tipo de dato, incluyendo valores primitivos, objetos, arrays, otras funciones e incluso valores indefinidos.

Devolución de valores primitivos

function obtenerNombre() {
  return "Ana"; // Devuelve un string
}

function obtenerEdad() {
  return 25; // Devuelve un número
}

function esMayorDeEdad(edad) {
  return edad >= 18; // Devuelve un booleano
}

console.log(obtenerNombre()); // Ana
console.log(obtenerEdad()); // 25
console.log(esMayorDeEdad(20)); // true

Devolución de objetos

function crearPersona(nombre, edad) {
  return {
    nombre: nombre,
    edad: edad,
    esMayorDeEdad: edad >= 18
  };
}

const persona = crearPersona("Carlos", 30);
console.log(persona.nombre); // Carlos
console.log(persona.esMayorDeEdad); // true

Devolución de arrays

function obtenerColores() {
  return ["rojo", "verde", "azul"];
}

const colores = obtenerColores();
console.log(colores[0]); // rojo
console.log(colores.length); // 3

Devolución de funciones

En JavaScript, las funciones son "ciudadanos de primera clase", lo que significa que pueden ser devueltas por otras funciones:

function crearSaludador(saludo) {
  // Devolvemos una nueva función
  return function(nombre) {
    return `${saludo}, ${nombre}!`;
  };
}

const saludarFormal = crearSaludador("Buenos días");
const saludarInformal = crearSaludador("Hola");

console.log(saludarFormal("Señor García")); // Buenos días, Señor García!
console.log(saludarInformal("Ana")); // Hola, Ana!

Este patrón se conoce como "fábrica de funciones" y es muy útil para crear funciones especializadas a partir de una base común.

Múltiples returns en una función

Una función puede tener múltiples sentencias return, aunque solo una de ellas se ejecutará en cada invocación:

function obtenerCalificacion(puntuacion) {
  if (puntuacion >= 90) {
    return "Sobresaliente";
  } else if (puntuacion >= 70) {
    return "Notable";
  } else if (puntuacion >= 60) {
    return "Bien";
  } else if (puntuacion >= 50) {
    return "Suficiente";
  } else {
    return "Insuficiente";
  }
}

console.log(obtenerCalificacion(95)); // Sobresaliente
console.log(obtenerCalificacion(65)); // Bien
console.log(obtenerCalificacion(30)); // Insuficiente

Este patrón es muy común para funciones que necesitan devolver diferentes valores según las condiciones.

Return implícito vs. explícito

En la mayoría de las declaraciones de funciones, debemos usar la palabra clave return explícitamente. Sin embargo, las funciones flecha tienen un comportamiento especial cuando se escriben en una sola línea sin llaves:

Return explícito (tradicional)

function duplicar(numero) {
  return numero * 2;
}

Return implícito (función flecha)

const duplicar = numero => numero * 2;

En el segundo ejemplo, el valor de numero * 2 se devuelve automáticamente sin necesidad de escribir return. Esto solo funciona cuando la función flecha se escribe como una expresión simple sin llaves.

Si añadimos llaves, debemos incluir el return explícitamente:

const duplicar = numero => {
  return numero * 2;
};

Funciones sin return (void)

No todas las funciones necesitan devolver un valor. Algunas funciones se usan principalmente por sus efectos secundarios, como modificar variables, actualizar el DOM o enviar datos a un servidor.

function saludar(nombre) {
  console.log(`Hola, ${nombre}!`);
  // No hay return explícito
}

const resultado = saludar("Luis");
console.log(resultado); // undefined

En este ejemplo, saludar() no tiene una sentencia return, por lo que implícitamente devuelve undefined. Esto es perfectamente válido cuando la función se usa por su efecto (mostrar un mensaje) y no por un valor de retorno.

Es importante destacar que todas las funciones en JavaScript devuelven algo, aunque no tengan una sentencia return explícita. Por defecto, devolverán undefined:

function sinRetorno() {
  let a = 5;
  a = a + 10;
  // Sin return explícito
}

console.log(sinRetorno()); // undefined

Return temprano para validaciones

Un patrón común en JavaScript es usar return temprano para validar condiciones antes de ejecutar el cuerpo principal de la función:

function dividir(a, b) {
  // Validación: evitar división por cero
  if (b === 0) {
    console.error("Error: División por cero");
    return null; // Return temprano
  }
  
  // Código principal que solo se ejecuta si la validación pasa
  return a / b;
}

console.log(dividir(10, 2)); // 5
console.log(dividir(10, 0)); // null (después de mostrar el error)

Este enfoque, conocido como "early return" (retorno temprano), hace que el código sea más legible y evita la anidación excesiva de bloques if/else.

Captura y uso de valores devueltos

Los valores devueltos por las funciones se pueden usar de diversas maneras:

Asignación a variables

function obtenerPrecioFinal(precio, impuesto) {
  return precio * (1 + impuesto);
}

const precioProducto = 100;
const iva = 0.21;
const precioFinal = obtenerPrecioFinal(precioProducto, iva);

console.log(`El precio final es: ${precioFinal}€`); // El precio final es: 121€

Uso directo en expresiones

function obtenerPrecio() {
  return 50;
}

function obtenerImpuesto() {
  return 0.21;
}

const total = obtenerPrecio() * (1 + obtenerImpuesto());
console.log(total); // 60.5

Encadenamiento de funciones

Los valores devueltos pueden pasarse directamente como argumentos a otras funciones:

function obtenerTexto() {
  return "   Hola Mundo   ";
}

function limpiarTexto(texto) {
  return texto.trim();
}

function contarCaracteres(texto) {
  return texto.length;
}

// Encadenamiento de funciones
const cantidadCaracteres = contarCaracteres(limpiarTexto(obtenerTexto()));
console.log(cantidadCaracteres); // 10

Uso con métodos de arrays

Los valores devueltos son especialmente útiles con métodos funcionales de arrays:

const numeros = [1, 2, 3, 4, 5];

// Usamos una función que devuelve un booleano
function esPar(numero) {
  return numero % 2 === 0;
}

const numerosPares = numeros.filter(esPar);
console.log(numerosPares); // [2, 4]

Patrones comunes y buenas prácticas

1. Coherencia en los tipos de retorno

Es recomendable que una función devuelva siempre el mismo tipo de datos, independientemente de la ruta de ejecución:

// No recomendado: devuelve diferentes tipos
function dividir(a, b) {
  if (b === 0) {
    return "Error: división por cero"; // String
  }
  return a / b; // Número
}

// Mejor enfoque: tipo de retorno consistente
function dividir(a, b) {
  if (b === 0) {
    return null; // O podría ser NaN
  }
  return a / b;
}

2. Preferir valores de retorno sobre efectos secundarios

Es mejor diseñar funciones que devuelvan valores en lugar de modificar variables externas:

// No recomendado: modifica una variable externa
let total = 0;
function sumar(a, b) {
  total = a + b; // Efecto secundario
}

// Mejor enfoque: devuelve un valor
function sumar(a, b) {
  return a + b; // Devuelve un valor, sin efectos secundarios
}
let total = sumar(5, 3);

3. Una única responsabilidad

Cada función debería tener una única responsabilidad clara, reflejada en su valor de retorno:

// No recomendado: hace demasiadas cosas
function procesarDatos(datos) {
  // Filtra datos
  const filtrados = datos.filter(item => item.activo);
  // Los guarda en algún lugar
  guardarEnBaseDeDatos(filtrados);
  // Actualiza la interfaz
  actualizarUI(filtrados);
  // Devuelve algo
  return filtrados.length;
}

// Mejor enfoque: funciones separadas con responsabilidades claras
function filtrarDatosActivos(datos) {
  return datos.filter(item => item.activo);
}

// Luego podemos usar el valor devuelto en otras funciones
const datosFiltrados = filtrarDatosActivos(datos);
guardarEnBaseDeDatos(datosFiltrados);
actualizarUI(datosFiltrados);

4. Documentar el valor de retorno

Es una buena práctica documentar claramente qué devuelve una función:

/**
 * Calcula el área de un círculo.
 * @param {number} radio - El radio del círculo
 * @returns {number} El área del círculo
 */
function calcularAreaCirculo(radio) {
  return Math.PI * radio * radio;
}

Resumen

La sentencia return es una herramienta fundamental en JavaScript que permite a las funciones devolver valores y finalizar su ejecución. Hemos visto cómo las funciones pueden devolver diferentes tipos de datos, desde valores primitivos hasta objetos complejos y otras funciones. También exploramos patrones comunes como múltiples returns, returns tempranos y la diferencia entre returns implícitos y explícitos.

Entender cómo trabajar con valores devueltos es esencial para crear código modular, reutilizable y fácil de mantener. Las funciones bien diseñadas que reciben parámetros claros y devuelven valores consistentes forman la base de una arquitectura de software sólida.

En el próximo artículo, exploraremos las diferentes formas de invocar funciones en JavaScript, completando nuestra comprensión sobre las funciones, que son bloques de construcción fundamentales en este lenguaje.