Skip to content

Funciones tipadas ​

Las funciones son donde TypeScript más te ayuda: al tipar lo que entra y lo que sale, el editor te avisa si alguien llama tu función con datos equivocados o si olvidas devolver algo. En esta lección tiparás parámetros, retornos, callbacks, funciones asíncronas y hasta sobrecargas.

Concepto ​

Una función tipada es como un contrato de servicio: "si me das esto, te devuelvo aquello". Los parámetros son lo que el cliente debe entregar y el tipo de retorno es lo que la función promete regresar. TypeScript vigila ambos lados del contrato.

ts
//                 entrada            salida
function sumar(a: number, b: number): number {
  return a + b;
}

sumar(2, 3);       // ✅ 5
sumar('2', 3);     // ❌ Argument of type 'string' is not assignable to parameter of type 'number'.
sumar(2);          // ❌ Expected 2 arguments, but got 1.

Parámetros y retorno ​

ts
// Declaración de función
function calcularIva(subtotal: number, tasa: number): number {
  return subtotal * tasa;
}

// Función flecha
const formatearPrecio = (monto: number, moneda: string): string =>
  new Intl.NumberFormat('es-MX', { style: 'currency', currency: moneda }).format(monto);

// El retorno se puede inferir, pero anotarlo protege el contrato
function crearSlug(titulo: string) {
  return titulo.toLowerCase().trim().replace(/\s+/g, '-');
} // TS infiere: string

// Si olvidas un camino de retorno, TS te avisa
function nivel(puntos: number): string {
  if (puntos > 100) return 'oro';
  if (puntos > 50) return 'plata';
  // ❌ Function lacks ending return statement and return type
  //    does not include 'undefined'.
}

Buena práctica

Anota siempre los parámetros (TS no puede inferirlos) y anota el retorno en funciones exportadas o importantes. Así, si cambias el cuerpo por error, el error aparece dentro de la función y no en los 20 lugares donde se usa.

Parámetros opcionales y por defecto ​

ts
// Opcional con ?: su tipo es string | undefined
function saludar(nombre: string, titulo?: string): string {
  return titulo ? `Hola, ${titulo} ${nombre}` : `Hola, ${nombre}`;
}
saludar('García');          // 'Hola, García'
saludar('García', 'Dra.');  // 'Hola, Dra. García'

// Por defecto: TS infiere el tipo del valor
function aplicarDescuento(precio: number, porcentaje = 10): number {
  return precio - precio * (porcentaje / 100);
}
aplicarDescuento(500);      // 450
aplicarDescuento(500, 25);  // 375

// Un objeto de opciones con valores por defecto (patrón muy común)
interface OpcionesToast {
  mensaje: string;
  tipo?: 'exito' | 'error' | 'info';
  duracion?: number;
}

function mostrarToast({ mensaje, tipo = 'info', duracion = 3000 }: OpcionesToast): void {
  console.log(`[${tipo}] ${mensaje} (${duracion} ms)`);
}
mostrarToast({ mensaje: 'Guardado', tipo: 'exito' });

Error común

Los parámetros opcionales van después de los obligatorios. function f(a?: string, b: number) da error. Si necesitas muchos opcionales, usa un objeto de opciones como en mostrarToast.

Parámetros rest ​

ts
function sumarTodos(...numeros: number[]): number {
  return numeros.reduce((a, b) => a + b, 0);
}
sumarTodos(1, 2, 3, 4); // 10

function registrar(nivel: 'info' | 'error', ...mensajes: string[]): void {
  console.log(`[${nivel.toUpperCase()}]`, ...mensajes);
}
registrar('error', 'Falló el pago', 'pedido 1024');

Tipos de función ​

Puedes describir la forma de una función como tipo, para reutilizarlo:

ts
// Un tipo que describe "función que recibe number y devuelve number"
type Operacion = (a: number, b: number) => number;

const multiplicar: Operacion = (a, b) => a * b;  // a y b se infieren
const dividir: Operacion = (a, b) => a / b;

// En interfaces
interface Validador {
  nombre: string;
  validar: (valor: string) => boolean;
}

const validadorEmail: Validador = {
  nombre: 'email',
  validar: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)
};

Callbacks ​

Tipar los callbacks hace que el código que los recibe y el que los pasa estén de acuerdo:

ts
interface Producto {
  nombre: string;
  precio: number;
}

// La función recibe un callback que decide si un producto pasa el filtro
function filtrarProductos(
  productos: Producto[],
  criterio: (p: Producto) => boolean
): Producto[] {
  return productos.filter(criterio);
}

const catalogo: Producto[] = [
  { nombre: 'Café', precio: 320 },
  { nombre: 'Taza', precio: 150 }
];

// p se infiere como Producto: tienes autocompletado
const baratos = filtrarProductos(catalogo, (p) => p.precio < 200);

// Callback que no devuelve nada: void
function alTerminar(callback: (error: Error | null, datos?: string) => void) {
  setTimeout(() => callback(null, 'listo'), 1000);
}

alTerminar((error, datos) => {
  if (error) return console.error(error.message);
  console.log(datos);
});

void en callbacks es permisivo

Un callback tipado como () => void sí puede devolver algo; simplemente se ignora. Por eso [1, 2].forEach((n) => lista.push(n)) funciona aunque push devuelva un número.

Funciones async y Promise ​

Una función async siempre devuelve una Promise. El tipo de retorno se escribe como Promise<Tipo>:

ts
interface Tarea {
  id: number;
  titulo: string;
  completada: boolean;
}

async function obtenerTareas(): Promise<Tarea[]> {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5');

  if (!res.ok) {
    throw new Error(`Error HTTP ${res.status}`);
  }

  const datos = await res.json();   // res.json() devuelve Promise<any>
  return datos.map((t: { id: number; title: string; completed: boolean }) => ({
    id: t.id,
    titulo: t.title,
    completada: t.completed
  }));
}

// Al usarla, TS sabe que tareas es Tarea[]
async function main() {
  try {
    const tareas = await obtenerTareas();
    tareas.forEach((t) => console.log(t.titulo));
  } catch (error) {
    // En el catch, error es unknown: compruébalo
    if (error instanceof Error) {
      console.error(error.message);
    }
  }
}

// Una función async que no devuelve nada
async function guardar(t: Tarea): Promise<void> {
  await fetch('/api/tareas', { method: 'POST', body: JSON.stringify(t) });
}

Error común

Olvidar el await. Si escribes const tareas = obtenerTareas();, tareas es Promise<Tarea[]>, no Tarea[], y TS te marcará error al hacer tareas.forEach. ¡Es TypeScript salvándote de un bug clásico!

Sobrecargas ​

A veces una función acepta combinaciones distintas de parámetros y devuelve tipos distintos según el caso. Las sobrecargas declaran cada firma por separado:

ts
// Firmas públicas (lo que ve quien usa la función)
function obtenerElemento(id: number): HTMLElement | null;
function obtenerElemento(ids: number[]): HTMLElement[];

// Implementación (no visible desde fuera; debe cubrir todos los casos)
function obtenerElemento(entrada: number | number[]): HTMLElement | null | HTMLElement[] {
  if (Array.isArray(entrada)) {
    return entrada
      .map((id) => document.getElementById(`item-${id}`))
      .filter((el): el is HTMLElement => el !== null);
  }
  return document.getElementById(`item-${entrada}`);
}

const uno = obtenerElemento(5);         // HTMLElement | null
const varios = obtenerElemento([1, 2]); // HTMLElement[]

Úsalas con moderación: muchas veces una unión o un genérico (próxima lección) resuelven lo mismo de forma más simple.

Tipar this ​

En funciones normales puedes declarar qué es this con un primer parámetro "falso" llamado this. No existe al ejecutar; solo informa a TS:

ts
interface Boton {
  etiqueta: string;
  clicks: number;
}

function manejarClick(this: Boton): void {
  this.clicks++;
  console.log(`${this.etiqueta}: ${this.clicks} clicks`);
}

const boton: Boton = { etiqueta: 'Comprar', clicks: 0 };
manejarClick.call(boton);  // ✅

manejarClick();
// ❌ The 'this' context of type 'void' is not assignable to method's 'this' of type 'Boton'.

En la práctica moderna verás poco this fuera de clases; repasa el tema en POO y clases.

Ejercicios ​

  1. Escribe calcularTotal(precio: number, cantidad: number, envio?: number): number que sume el envío solo si se pasa.
  2. Define type Formateador = (valor: number) => string y crea dos funciones con ese tipo: una que formatee como moneda y otra como porcentaje.
  3. Escribe reintentar(fn: () => Promise<string>, intentos: number): Promise<string> que ejecute fn y, si falla, la vuelva a intentar hasta agotar los intentos.
  4. Escribe agruparPor(productos, obtenerClave) donde obtenerClave es un callback que recibe un Producto y devuelve un string; el resultado es un Record<string, Producto[]>.
Ver solución
ts
// 1
function calcularTotal(precio: number, cantidad: number, envio?: number): number {
  return precio * cantidad + (envio ?? 0);
}

// 2
type Formateador = (valor: number) => string;

const comoMoneda: Formateador = (v) =>
  new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(v);

const comoPorcentaje: Formateador = (v) => `${(v * 100).toFixed(1)}%`;

// 3
async function reintentar(fn: () => Promise<string>, intentos: number): Promise<string> {
  let ultimoError: unknown;
  for (let i = 0; i < intentos; i++) {
    try {
      return await fn();
    } catch (error) {
      ultimoError = error;
    }
  }
  throw ultimoError;
}

// 4
interface Producto {
  nombre: string;
  categoria: string;
  precio: number;
}

function agruparPor(
  productos: Producto[],
  obtenerClave: (p: Producto) => string
): Record<string, Producto[]> {
  const grupos: Record<string, Producto[]> = {};
  for (const p of productos) {
    const clave = obtenerClave(p);
    (grupos[clave] ??= []).push(p);
  }
  return grupos;
}

agruparPor(
  [{ nombre: 'Café', categoria: 'bebidas', precio: 320 }],
  (p) => p.categoria
);

Resumen ​

  • Tipa parámetros siempre; tipa el retorno en funciones exportadas.
  • ? para parámetros opcionales (al final); valores por defecto se infieren.
  • ...rest: Tipo[] para cantidad variable de argumentos.
  • type Fn = (a: A) => B describe funciones reutilizables, ideal para callbacks.
  • Las funciones async devuelven Promise<Tipo>; en catch, el error es unknown.
  • Las sobrecargas declaran varias firmas; úsalas solo cuando una unión no basta.
  • this se puede tipar con un primer parámetro especial.

Siguiente: Genéricos

Academia Web — aprende, copia y construye.