TypeScript

TypeScript

Tutorial TypeScript: Control de flujo

Aprende control de flujo en TypeScript con if-else, switch, bucles y control de iteraciones para escribir código eficiente y legible.

Aprende TypeScript y certifícate

Estructuras condicionales if-else

Las estructuras condicionales son fundamentales en cualquier lenguaje de programación, ya que permiten ejecutar diferentes bloques de código dependiendo de si una condición se cumple o no. En TypeScript, las estructuras condicionales if-else funcionan de manera similar a JavaScript, pero con la ventaja adicional del sistema de tipos.

Sintaxis básica

La estructura más simple es el if, que ejecuta un bloque de código solo cuando la condición evaluada es verdadera:

if (condition) {
  // Código que se ejecuta si la condición es true
}

Donde condition debe ser una expresión que se evalúe como un valor booleano (true o false). TypeScript verificará en tiempo de compilación que esta expresión sea compatible con un tipo booleano.

Estructura if-else

Cuando necesitamos ejecutar un bloque alternativo cuando la condición es falsa, utilizamos la estructura if-else:

if (condition) {
  // Código que se ejecuta si la condición es true
} else {
  // Código que se ejecuta si la condición es false
}

Ejemplo práctico

let miNumero = 10;

// Pregunta: ¿Es miNumero mayor que 0? (10 > 0)
if (miNumero > 0) { // La respuesta es TRUE (Sí)
  console.log("El número es positivo"); // Se mostrara este mensaje
} else {
  console.log("El número no es positivo"); // Este mensaje NO se muestra
}

let otroNumero = -5;

// Pregunta: ¿Es otroNumero mayor que 0? (-5 > 0)
if (otroNumero > 0) { // La respuesta es FALSE (No)
   console.log("El número es positivo"); // Este mensaje NO se muestra
} else {
   console.log("El número no es positivo"); // ¡ESTE mensaje SÍ se muestra!
}

Múltiples condiciones con else-if

El programa revisa la primera condición. Si es true, ejecuta ese código y se olvida de todo lo demás. Si no fue true, revisa la segunda condición (else if). Si esta es true, ejecuta ese código y se olvida del resto. Y así sigue con cada else if. Si llega al final y ninguna condición fue true, entonces (y solo entonces) ejecuta el bloque else final, si existe.

function getTrafficLightColor(color: string): string {
  // Pregunta 1: ¿El color es exactamente "red"?
  if (color === "red") {
    return "¡Alto!"; // Hara esto y ya (no mira los demás else if/else)
  } // Si fue No, sigue a la siguiente pregunta...

  // Pregunta 2 (solo si no era "red"): ¿El color es exactamente "yellow"?
  else if (color === "yellow") {
    return "¡Precaución!"; // Si Sí, haz esto y ya
  } // Si fue No, sigue a la siguiente pregunta...

  // Pregunta 3 (solo si no era "red" ni "yellow"): ¿El color es exactamente "green"?
  else if (color === "green") {
    return "¡Adelante!";
  } // Si fue No, sigue a la siguiente parte...

  // Si NINGUNA de las preguntas anteriores fue "Sí":
  else {
    return "Color de semáforo inválido"; 
  }
}

console.log(getTrafficLightColor("red"));    // Pregunta 1: ¿"red" === "red"? -> Sí. Resultado: "¡Alto!"
console.log(getTrafficLightColor("blue"));   // Pregunta 1: No. Pregunta 2: No. Pregunta 3: No. Resultado: Va al final "else" -> "Color inválido"

Condiciones anidadas

También podemos anidar estructuras if dentro de otras:

function checkEligibility(age: number, hasLicense: boolean): string {

  // Primera pregunta (la más importante): ¿Tienes 18 años o más?
  if (age >= 18) {
    // SI la respuesta a la primera pregunta es "Sí"...
    // ...entramos aquí. Ahora, DENTRO de este caso, hacemos la SEGUNDA pregunta:
    // Pregunta 2 ¿Tienes licencia? (Recuerda, hasLicense ya es true o false)
    if (hasLicense) {
      // Este código se ejecuta si la respuesta a la Pregunta 1 fue "Sí" Y la respuesta a la Pregunta 2 fue "Sí".
      return "Puedes conducir";
    } else {
      // Este código se ejecuta si la respuesta a la Pregunta 1 fue "Sí" PERO la respuesta a la Pregunta 2 fue "No".
      return "Necesitas obtener una licencia primero";
    }
  } // Si la respuesta a la PRIMERA pregunta fue "No"...
  else {
    // ...entramos directamente a este bloque. No importa si tuviera licencia o no, la edad es el primer filtro.
    return "Eres demasiado joven para conducir";
  }
}

console.log(checkEligibility(20, true));  // Pregunta 1: ¿20>=18? -> Sí. Entra. Pregunta 2: ¿true? -> Sí. Resultado: "Puedes conducir"
console.log(checkEligibility(20, false)); // Pregunta 1: ¿20>=18? -> Sí. Entra. Pregunta 2: ¿false? -> No. Resultado: "Necesitas obtener..."
console.log(checkEligibility(16, false)); // Pregunta 1: ¿16>=18? -> No. Va directo al else principal. Resultado: "Eres demasiado joven..."

Operadores lógicos en condiciones

Podemos combinar múltiples condiciones utilizando operadores lógicos:

  • && (AND): La condición completa será Verdadera solo si la parte de la izquierda es Verdadera Y además la parte de la derecha también es Verdadera. Si alguna es Falsa, el resultado es Falso.
  • || (OR): La condición completa será Verdadera si la parte de la izquierda es Verdadera O bien la parte de la derecha es Verdadera (o si ambas lo son). Solo si AMBAS son Falsas, el resultado es Falso.
  • ! (NOT): Este operador cambia el resultado de una condición. Si algo era Verdadero, ! lo vuelve Falso. Si algo era Falso, ! lo vuelve Verdadero.
// Ejemplo &&
let tieneDinero = true;
let tieneHambre = true;
if (tieneDinero && tieneHambre) { // ¿Tiene dinero Y ADEMÁS tiene hambre? (true && true) -> true
    console.log("Puede comer"); // Este se ejecuta
}

// Ejemplo ||
let esFinDeSemana = false;
let esFestivo = true;
if (esFinDeSemana || esFestivo) { // ¿Es fin de semana O BIEN es festivo? (false || true) -> true
    console.log("Puedes descansar"); // Este se ejecuta
}

// Ejemplo !
let estaLloviendo = false;
if (!estaLloviendo) { // ¿NO está lloviendo? (!false) -> true
    console.log("Sal a pasear"); // Este se ejecuta
}

Operador ternario

Para condiciones simples, podemos utilizar el operador ternario como alternativa más concisa, es una forma corta de escribir un if-else simple, cuando lo único que quieres es elegir entre dos valores según si la condición es Verdadera o Falsa

condition ? expressionIfTrue : expressionIfFalse

Ejemplo:

// Usando if-else:
function getStatusIFELSE(isActive: boolean): string {
  if (isActive) {
    return "Active";
  } else {
    return "Inactive";
  }
}

// Usando el Operador Ternario (la forma corta para este caso):
function getStatusTernario(isActive: boolean): string {
  return isActive ? "Active" : "Inactive";
  // Se lee: ¿Es 'isActive' Verdadera?
  // SI (?), devuelve "Active" (:), SI NO, devuelve "Inactive"
}

Narrowing de tipos con if

Una característica poderosa de TypeScript es el narrowing (estrechamiento) de tipos, imagina que tienes una variable que puede ser de diferentes tipos. El "estrechamiento de tipos" es como decirle a TypeScript: "En este momento, esta variable es específicamente de este tipo".

function printLength(value: string | number): void {
  if (typeof value === "string") {
    // TypeScript sabe que value es string aquí
    console.log(value.length);
  } else {
    // TypeScript sabe que value es number aquí
    console.log(value.toFixed(2));
  }
}

printLength("hello"); // 5
printLength(42.123);  // 42.12

Comprobación de nulabilidad

Antes de usar una variable, es buena práctica comprobar si tiene un valor real o es null/undefined.

function greet(name: string | null): string {
  if (name === null) {
    return "Hello, guest!";
  } else {
    return `Hello, ${name}!`;
  }
}

console.log(greet("John")); // "Hello, John!"
console.log(greet(null));   // "Hello, guest!"

Truthy y Falsy

En Typescript al igual que en JavaScript, algunos valores se consideran "falsos" automáticamente:

  • false - Obviamente falso
  • 0 - El número cero
  • "" - Un texto vacío
  • null - Valor nulo
  • undefined - Valor no definido
  • NaN - "No es un número"
function processValue(value: string | number | null | undefined): string {
  if (value) {
    // Se ejecuta si value es truthy
    return `Processing: ${value}`;
  } else {
    // Se ejecuta si value es falsy
    return "No value to process";
  }
}

console.log(processValue("data")); // "Processing: data"
console.log(processValue(0));      // "No value to process"
console.log(processValue(null));   // "No value to process"

Buenas prácticas

  • Simplicidad: Mantén las condiciones lo más simples posible.
  • Evita negaciones: Las condiciones positivas son más fáciles de entender que las negativas.
  • Usa llaves: Aunque para bloques de una sola línea las llaves son opcionales, usarlas siempre mejora la legibilidad y previene errores.
  • Considera alternativas: Para múltiples condiciones, evalúa si un switch podría ser más apropiado.
// Mejor
if (isValid) {
  processData();
}

// Evitar
if (!isInvalid) {
  processData();
}

Las estructuras condicionales if-else son herramientas esenciales para controlar el flujo de ejecución en tus programas TypeScript, permitiéndote crear lógica compleja y tomar decisiones basadas en diferentes condiciones.

Switch case en TypeScript

El switch case es una estructura de control que proporciona una forma elegante de manejar múltiples condiciones cuando necesitamos comparar una variable contra valores específicos. A diferencia de las estructuras if-else encadenadas, el switch está optimizado para evaluar una expresión contra varios casos posibles.

Sintaxis básica

La estructura básica de un switch en TypeScript es:

switch (expression) {
  case value1:
    // Código a ejecutar si expression === value1
    break;
  case value2:
    // Código a ejecutar si expression === value2
    break;
  default:
    // Código a ejecutar si ningún caso coincide
}

Donde:

  • expression: Es la expresión que se evalúa una sola vez
  • case value: Define un valor posible para comparar con la expresión
  • break: Termina la ejecución del bloque switch
  • default: Bloque opcional que se ejecuta cuando ningún caso coincide

Ejemplo simple

function getWeekdayMessage(day: number): string {
  switch (day) {
    case 1:
      return "It's Monday, the week just started";
    case 2:
      return "It's Tuesday";
    case 3:
      return "It's Wednesday, halfway there";
    case 4:
      return "It's Thursday";
    case 5:
      return "It's Friday, weekend is coming!";
    case 6:
    case 7:
      return "It's weekend!";
    default:
      return "Invalid day number";
  }
}

console.log(getWeekdayMessage(5)); // "It's Friday, weekend is coming!"
console.log(getWeekdayMessage(7)); // "It's weekend!"

En este ejemplo, observa cómo los casos 6 y 7 comparten el mismo bloque de código, una característica útil del switch.

La importancia del break

Si se omite la sentencia break al final de un bloque case (y no hay otra sentencia como return que salga de la función), la ejecución no se detendrá al finalizar ese bloque. En su lugar, la ejecución continuará secuencialmente hacia el siguiente bloque case (y subsiguientes), ejecutando su código también, independientemente de si el valor de ese case coincide o no con la expresión original del switch. Este comportamiento se denomina "fall-through" (literalmente, "caída" o ejecución secuencial a través de los casos).

function demonstrateFallthrough(value: number): void {
  switch (value) {
    case 1:
      console.log("One");
      // Sin break, continúa al siguiente caso
    case 2:
      console.log("Two");
      break;
    case 3:
      console.log("Three");
      break;
  }
}

demonstrateFallthrough(1); 
// Imprime:
// "One"
// "Two"

Por lo tanto, incluir break; al final de cada bloque case (a menos que se desee intencionadamente la caída o que una sentencia como return ya finalice la ejecución de la función) es crucial para asegurar que el switch funcione como se espera, ejecutando únicamente el código asociado al case coincidente.

Casos agrupados

Podemos agrupar casos que comparten la misma lógica:

function getSeasonByMonth(month: number): string {
  switch (month) {
    case 12:
    case 1:
    case 2:
      return "Winter";
    case 3:
    case 4:
    case 5:
      return "Spring";
    case 6:
    case 7:
    case 8:
      return "Summer";
    case 9:
    case 10:
    case 11:
      return "Autumn";
    default:
      return "Invalid month";
  }
}

console.log(getSeasonByMonth(7)); // "Summer"

Aquí aprovechamos que sin break, el código sigue cayendo hasta encontrar un return o break

Switch con strings

El switch también funciona con cadenas de texto, lo que lo hace muy versátil:

function getColorHex(colorName: string): string {
  switch (colorName.toLowerCase()) {
    case "red":
      return "#FF0000";
    case "green":
      return "#00FF00";
    case "blue":
      return "#0000FF";
    case "black":
      return "#000000";
    case "white":
      return "#FFFFFF";
    default:
      return "Unknown color";
  }
}

console.log(getColorHex("Blue")); // "#0000FF"
console.log(getColorHex("YELLOW")); // "Unknown color"

Switch con expresiones

La expresión evaluada puede ser más compleja que una simple variable:

function getDiscount(age: number, isStudent: boolean): number {
  switch (true) {
    case age < 12:
      return 0.5; // 50% discount for children
    case age >= 65:
      return 0.3; // 30% discount for seniors
    case isStudent:
      return 0.2; // 20% discount for students
    default:
      return 0; // No discount
  }
}

console.log(getDiscount(10, false)); // 0.5
console.log(getDiscount(25, true));  // 0.2
console.log(getDiscount(70, false)); // 0.3

En este ejemplo, switch(true) significa "busca el primer caso que sea verdadero". Es como una forma elegante de escribir varios if-else.

Narrowing de tipos con switch

Al igual que con if, TypeScript realiza narrowing de tipos dentro de los bloques case:

function processValue(value: string | number): void {
  switch (typeof value) {
    case "string":
      // TypeScript sabe que value es string aquí
      console.log(value.toUpperCase());
      break;
    case "number":
      // TypeScript sabe que value es number aquí
      console.log(value.toFixed(2));
      break;
  }
}

processValue("hello"); // "HELLO"
processValue(42.123);  // "42.12"

Switch con enums

Los switch funcionan especialmente bien con enums en TypeScript:

enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  ServerError = 500
}

function getStatusMessage(status: HttpStatus): string {
  switch (status) {
    case HttpStatus.OK:
      return "Request successful";
    case HttpStatus.Created:
      return "Resource created successfully";
    case HttpStatus.BadRequest:
      return "Invalid request parameters";
    case HttpStatus.Unauthorized:
      return "Authentication required";
    case HttpStatus.NotFound:
      return "Resource not found";
    case HttpStatus.ServerError:
      return "Internal server error";
    default:
      return "Unknown status code";
  }
}

console.log(getStatusMessage(HttpStatus.NotFound)); // "Resource not found"

Exhaustividad en switch

Una característica poderosa de TypeScript es la verificación de exhaustividad en los switch. Cuando se usa con tipos discriminados o uniones, TypeScript puede detectar si has cubierto todos los casos posibles:

type Shape = 
  | { kind: "circle"; radius: number }
  | { kind: "square"; sideLength: number }
  | { kind: "rectangle"; width: number; height: number };

function calculateArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
    case "rectangle":
      return shape.width * shape.height;
    default:
      // Esta línea asegura que todos los casos están cubiertos
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}

const circle: Shape = { kind: "circle", radius: 5 };
console.log(calculateArea(circle)); // 78.54...

Beneficios de la verificación exhaustiva

La verificación de exhaustividad proporciona seguridad adicional durante el desarrollo:

  1. Detección temprana de errores: Si se añade un nuevo tipo a la unión, TypeScript generará un error de compilación indicando que no se ha manejado ese caso.
  2. Documentación implícita: El código comunica claramente que se espera manejar todos los casos posibles.
  3. Mantenimiento robusto: Obliga a actualizar la lógica de manejo cuando se introducen nuevos tipos.

Cuándo usar switch vs if-else

El switch es más adecuado cuando:

  • Comparas una única variable contra múltiples valores constantes
  • Tienes muchas condiciones alternativas (más de 2-3)
  • Necesitas agrupar casos que comparten la misma lógica

Por otro lado, if-else es mejor cuando:

  • Evalúas condiciones diferentes no relacionadas con una sola variable
  • Tienes pocas alternativas (2-3 condiciones)
  • Las condiciones implican rangos o expresiones complejas
// Mejor con switch
function getGrade(score: number): string {
  switch (Math.floor(score / 10)) {
    case 10:
    case 9:
      return "A";
    case 8:
      return "B";
    case 7:
      return "C";
    case 6:
      return "D";
    default:
      return "F";
  }
}

console.log(getGrade(95)); // "A"
console.log(getGrade(81)); // "B"
console.log(getGrade(55)); // "F"

La estructura switch proporciona una sintaxis clara y concisa para manejar múltiples condiciones basadas en una sola expresión, mejorando la legibilidad del código cuando se trabaja con conjuntos de valores discretos.

Bucles for, while y do-while

Los bucles son estructuras fundamentales en programación que permiten ejecutar un bloque de código repetidamente. TypeScript, al igual que JavaScript, ofrece varios tipos de bucles para controlar la iteración sobre datos o ejecutar código mientras se cumpla una condición específica.

Bucle for

El bucle for es probablemente el más utilizado cuando conocemos de antemano el número de iteraciones que necesitamos realizar. Su sintaxis consta de tres partes: inicialización, condición y expresión final.

for (inicialización; condición; expresión de iteración) {
  // Bloque de código que se ejecutará en cada repetición del bucle.
}
  • Inicialización: Se ejecuta una única vez al comienzo del bucle, antes de que inicie la primera iteración. Su propósito habitual es declarar e inicializar una variable que actuará como contador o punto de partida para la iteración (ej. let i = 0;).
  • Condición: Se evalúa antes de cada nueva iteración. Si el resultado de esta evaluación es true (verdadero), el bloque de código dentro del bucle se ejecuta. Si el resultado es false (falso), el bucle finaliza, y la ejecución del programa continúa con la sentencia que sigue a la estructura for. (ej. i < 10;).
  • Expresión de Iteración: Se ejecuta automáticamente al finalizar cada iteración completa del bloque de código del bucle. Su función principal es actualizar la variable de control (o el estado relevante para la condición) de manera que, en algún momento, la condición se vuelva falsa y el bucle termine.

Un ejemplo básico:

for (let i = 0; i < 5; i++) {
  console.log(`Iteration ${i}`);
}
// Imprime:
// Iteration 0
// Iteration 1
// Iteration 2
// Iteration 3
// Iteration 4

El bucle for clásico es particularmente adecuado para acceder y procesar elementos de un array basándose en su posición numérica (índice):

const fruits: string[] = ["Apple", "Banana", "Cherry", "Dragon fruit"];

for (let i = 0; i < fruits.length; i++) {
  console.log(`Fruit ${i + 1}: ${fruits[i]}`);
}
// Imprime:
// Fruit 1: Apple
// Fruit 2: Banana
// Fruit 3: Cherry
// Fruit 4: Dragon fruit

Bucle for...of

TypeScript incluye el bucle for...of como una forma más concisa y legible de iterar sobre los elementos (valores) de colecciones que son "iterables". Esto incluye Arrays ([]), Strings (""), Mapas (Map), Conjuntos (Set), y otros tipos de datos.

Su sintaxis es más sencilla que la del for clásico, ya que abstrae la gestión explícita de un índice:

for (const elemento of coleccionIterable) {
  // Bloque de código que se ejecutará para cada 'elemento' individual de la colección.
}
const colors: string[] = ["red", "green", "blue"];

for (const color of colors) {
  console.log(`Color: ${color}`);
}
// Imprime:
// Color: red
// Color: green
// Color: blue

Este bucle es más limpio que el for tradicional cuando solo necesitamos acceder a los valores y no nos importa el índice.

Bucle for...in

El bucle for...in itera sobre las propiedades enumerables de un objeto:

const person = { // Objeto con propiedades
  name: "Alice",
  age: 30,
  job: "Developer"
};

// Por cada "key" presente en el objeto "person"
for (const key in person) { 
// "key" contendra el nombre de la propiedad como un string ("name", luego "age"...)
// Usamos la clave (el nombre de la propiedad) para acceder a su valor en el objeto.
  console.log(`${key}: ${person[key as keyof typeof person]}`);-
}
// Imprime:
// name: Alice
// age: 30
// job: Developer

Nota: En TypeScript, necesitamos usar una aserción de tipo (keyof typeof person) para acceder a las propiedades dinámicamente, ya que el compilador no puede inferir el tipo de la propiedad en tiempo de compilación.

Es importante recordar que for...in no está diseñado para iterar sobre arrays, ya que también incluirá propiedades no numéricas y puede no seguir un orden específico:

const numbers = [10, 20, 30];
numbers.customProperty = "test"; // Propiedad personalizada

for (const key in numbers) {
  console.log(key); // Imprime: "0", "1", "2", "customProperty"
}

Bucle while

El bucle while ejecuta un bloque de código mientras una condición especificada sea verdadera:

while (condición) {
  // Código a ejecutar mientras la condición sea true
}

Este bucle es útil cuando no sabemos cuántas iteraciones necesitaremos:

let countdown = 5;

while (countdown > 0) {
  console.log(`Countdown: ${countdown}`);
  countdown--;
}
console.log("Liftoff!");
// Imprime:
// Countdown: 5
// Countdown: 4
// Countdown: 3
// Countdown: 2
// Countdown: 1
// Liftoff!

Un caso de uso común es procesar datos hasta encontrar cierta condición:

const searchText = "TypeScript";
const textToSearch = "JavaScript is great, but TypeScript adds static typing";
let position = 0;
let found = false;

while (position < textToSearch.length) {
  if (textToSearch.substring(position, position + searchText.length) === searchText) {
    found = true;
    break;
  }
  position++;
}

console.log(found ? `Found at position ${position}` : "Not found");
// Imprime: Found at position 25

Bucle do-while

El bucle do-while es similar al while, pero garantiza que el bloque de código se ejecute al menos una vez, ya que la condición se evalúa después de cada iteración:

do {
  // Código a ejecutar al menos una vez
} while (condición);

Este bucle es útil cuando necesitamos ejecutar el código al menos una vez, independientemente de la condición:

let userInput = "";
let attempts = 0;

do {
  // Simulamos la entrada del usuario
  attempts++;
  if (attempts === 3) {
    userInput = "quit";
  }
  
  console.log(`Attempt ${attempts}: ${userInput || "No input"}`);
} while (userInput !== "quit" && attempts < 5);

// Imprime:
// Attempt 1: No input
// Attempt 2: No input
// Attempt 3: quit

Bucles anidados

Podemos anidar bucles para trabajar con estructuras de datos multidimensionales:

const matrix: number[][] = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

for (let i = 0; i < matrix.length; i++) {
  let row = "";
  for (let j = 0; j < matrix[i].length; j++) {
    row += matrix[i][j] + " ";
  }
  console.log(row);
}
// Imprime:
// 1 2 3 
// 4 5 6 
// 7 8 9 

Optimización de bucles

Para mejorar el rendimiento, especialmente en bucles grandes, podemos aplicar algunas optimizaciones:

  • Caching de la longitud: Evita recalcular la longitud en cada iteración
const items = ["a", "b", "c", "d", "e"];

// Menos eficiente
for (let i = 0; i < items.length; i++) {
  console.log(items[i]);
}

// Más eficiente
for (let i = 0, len = items.length; i < len; i++) {
  console.log(items[i]);
}
  • Iteración inversa: En algunos casos, iterar desde el final puede ser más eficiente
const largeArray = new Array(1000).fill(0);

for (let i = largeArray.length - 1; i >= 0; i--) {
  // Procesar elementos
}

Bucles y tipos genéricos

TypeScript permite crear bucles con tipos genéricos para mantener la seguridad de tipos:

function processItems<T>(items: T[], processor: (item: T) => void): void {
  for (const item of items) {
    processor(item);
  }
}

const numbers = [1, 2, 3, 4, 5];
processItems(numbers, (num) => {
  console.log(num * 2);
});

const names = ["Alice", "Bob", "Charlie"];
processItems(names, (name) => {
  console.log(`Hello, ${name}!`);
});

Bucles asíncronos

Para operaciones asíncronas dentro de bucles, podemos usar async/await:

async function processFilesSequentially(fileUrls: string[]): Promise<void> {
  for (const url of fileUrls) {
    try {
      // Simulamos una operación asíncrona
      const data = await fetchFile(url);
      console.log(`Processed ${url}: ${data.length} bytes`);
    } catch (error) {
      console.error(`Error processing ${url}:`, error);
    }
  }
}

// Función simulada para el ejemplo
async function fetchFile(url: string): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`Content of ${url}`), 100);
  });
}

// Uso
processFilesSequentially([
  "file1.txt",
  "file2.txt",
  "file3.txt"
]);

Cuándo usar cada tipo de bucle

  • for: Cuando conoces el número exacto de iteraciones o necesitas un contador.
  • for...of: Para iterar sobre elementos de colecciones iterables de forma simple.
  • for...in: Para recorrer propiedades de objetos (no recomendado para arrays).
  • while: Cuando no sabes cuántas iteraciones necesitarás y la condición debe evaluarse antes de cada iteración.
  • do-while: Cuando necesitas ejecutar el código al menos una vez, independientemente de la condición.

Consideraciones de rendimiento

  • Los bucles for tradicionales suelen ser los más rápidos para arrays grandes.
  • for...of es más legible pero ligeramente menos eficiente que for.
  • Evita modificar la colección que estás iterando durante el bucle.
  • Para operaciones intensivas, considera usar métodos como forEach, map, filter y reduce que pueden ser más declarativos y legibles.
// Ejemplo de métodos funcionales vs bucles
const numbers = [1, 2, 3, 4, 5];

// Usando bucle for
const doubledFor: number[] = [];
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    doubledFor.push(numbers[i] * 2);
  }
}

// Usando métodos funcionales
const doubledFunctional = numbers
  .filter(num => num % 2 === 0)
  .map(num => num * 2);

console.log(doubledFor);         // [4, 8]
console.log(doubledFunctional);  // [4, 8]

Los bucles son herramientas esenciales para controlar el flujo de ejecución en tus programas TypeScript. Elegir el tipo de bucle adecuado para cada situación te ayudará a escribir código más eficiente, legible y mantenible.

Control de iteraciones (break, continue)

El control de iteraciones es una parte fundamental de la programación que nos permite modificar el comportamiento normal de los bucles. En TypeScript, disponemos de dos instrucciones principales para este propósito: break y continue. Estas instrucciones nos permiten tener un control más preciso sobre cómo se ejecutan nuestros bucles.

Instrucción break

La instrucción break permite terminar inmediatamente la ejecución de un bucle, saltando a la primera línea de código después del bucle. Es especialmente útil cuando encontramos la condición que buscábamos y no necesitamos seguir iterando.

const numbers = [1, 5, 7, 12, 20, 30];
let foundNumber = -1;

for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] > 10) {
    foundNumber = numbers[i];
    break; // Sale del bucle inmediatamente
  }
}

console.log(`First number greater than 10: ${foundNumber}`); // First number greater than 10: 12

En este ejemplo, una vez que encontramos el primer número mayor que 10, no necesitamos seguir buscando, por lo que usamos break para salir del bucle.

Instrucción continue

La instrucción continue salta a la siguiente iteración del bucle, omitiendo el resto del código en la iteración actual. Es útil cuando queremos omitir ciertos elementos pero continuar procesando los demás.

const mixedArray = [1, "hello", 3, null, 5, undefined, 7];
const validNumbers: number[] = [];

for (let i = 0; i < mixedArray.length; i++) {
  // Omitimos elementos que no son números
  if (typeof mixedArray[i] !== "number") {
    continue; // Salta a la siguiente iteración
  }
  
  validNumbers.push(mixedArray[i] as number);
}

console.log(validNumbers); // [1, 3, 5, 7]

En este caso, usamos continue para omitir cualquier elemento que no sea un número, procesando solo los valores numéricos.

Uso con bucles anidados

Las instrucciones break y continue afectan solo al bucle más interno en el que se encuentran. Para controlar bucles externos, podemos usar etiquetas (labels):

outerLoop: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) {
      console.log(`Breaking out of both loops at i=${i}, j=${j}`);
      break outerLoop; // Sale de ambos bucles
    }
    console.log(`i=${i}, j=${j}`);
  }
}

Este código imprimirá:

i=0, j=0
i=0, j=1
i=0, j=2
i=1, j=0
Breaking out of both loops at i=1, j=1

Patrones comunes con break

Búsqueda eficiente

El patrón de búsqueda es uno de los usos más comunes de break:

function findUserById(users: Array<{id: number, name: string}>, targetId: number) {
  for (const user of users) {
    if (user.id === targetId) {
      return user; // El return implícitamente actúa como un break
    }
  }
  return null; // Usuario no encontrado
}

const users = [
  {id: 1, name: "Alice"},
  {id: 2, name: "Bob"},
  {id: 3, name: "Charlie"}
];

console.log(findUserById(users, 2)); // {id: 2, name: "Bob"}

Validación con break

Podemos usar break para salir de un bucle cuando encontramos datos inválidos:

function validateData(data: any[]): boolean {
  let isValid = true;
  
  for (const item of data) {
    if (item === null || item === undefined) {
      isValid = false;
      break; // No necesitamos seguir validando
    }
  }
  
  return isValid;
}

console.log(validateData([1, 2, 3])); // true
console.log(validateData([1, null, 3])); // false

Patrones comunes con continue

Filtrado de elementos

El uso más común de continue es para filtrar elementos que no cumplen ciertos criterios:

function processEvenNumbers(numbers: number[]): number[] {
  const result: number[] = [];
  
  for (const num of numbers) {
    if (num % 2 !== 0) {
      continue; // Omitimos números impares
    }
    
    result.push(num * 2); // Procesamos solo números pares
  }
  
  return result;
}

console.log(processEvenNumbers([1, 2, 3, 4, 5, 6])); // [4, 8, 12]

Manejo de casos especiales

Podemos usar continue para manejar casos especiales sin complicar el flujo principal:

function calculateDiscounts(prices: number[]): number[] {
  const discountedPrices: number[] = [];
  
  for (const price of prices) {
    // Casos especiales
    if (price <= 0) {
      discountedPrices.push(0);
      continue;
    }
    
    if (price >= 1000) {
      discountedPrices.push(price * 0.8); // 20% descuento
      continue;
    }
    
    // Caso normal: 10% descuento
    discountedPrices.push(price * 0.9);
  }
  
  return discountedPrices;
}

console.log(calculateDiscounts([-10, 50, 1200, 300])); // [0, 45, 960, 270]

Break y continue con while y do-while

Estas instrucciones funcionan igual en bucles while y do-while:

let i = 0;
while (i < 10) {
  i++;
  
  if (i % 2 === 0) {
    continue; // Salta los números pares
  }
  
  console.log(i); // Imprime solo números impares
  
  if (i >= 7) {
    break; // Termina cuando i llega a 7
  }
}
// Imprime: 1, 3, 5, 7

Optimización con break

Podemos usar break para optimizar algoritmos, evitando iteraciones innecesarias:

function isPrime(num: number): boolean {
  if (num <= 1) return false;
  if (num <= 3) return true;
  
  // Solo necesitamos verificar hasta la raíz cuadrada
  const limit = Math.sqrt(num);
  
  for (let i = 2; i <= limit; i++) {
    if (num % i === 0) {
      return false; // No es primo, salimos inmediatamente
    }
  }
  
  return true;
}

console.log(isPrime(17)); // true
console.log(isPrime(15)); // false

Combinando break y continue

En algoritmos más complejos, podemos combinar ambas instrucciones:

function findFirstValidPair(numbers: number[]): [number, number] | null {
  for (let i = 0; i < numbers.length; i++) {
    // Omitimos números negativos
    if (numbers[i] < 0) {
      continue;
    }
    
    for (let j = i + 1; j < numbers.length; j++) {
      // Omitimos números negativos
      if (numbers[j] < 0) {
        continue;
      }
      
      // Si la suma es par, encontramos un par válido
      if ((numbers[i] + numbers[j]) % 2 === 0) {
        return [numbers[i], numbers[j]];
      }
    }
  }
  
  return null; // No se encontró ningún par válido
}

console.log(findFirstValidPair([1, -2, 3, 4, -5])); // [1, 3]

Alternativas modernas a break y continue

En TypeScript moderno, a menudo podemos reemplazar bucles con break y continue por métodos de array más declarativos:

// En lugar de:
const evenNumbers: number[] = [];
for (const num of [1, 2, 3, 4, 5]) {
  if (num % 2 !== 0) {
    continue;
  }
  evenNumbers.push(num);
}

// Podemos usar:
const evenNumbersModern = [1, 2, 3, 4, 5].filter(num => num % 2 === 0);

console.log(evenNumbersModern); // [2, 4]
// En lugar de:
let firstEven: number | undefined;
for (const num of [1, 3, 5, 6, 7, 8]) {
  if (num % 2 === 0) {
    firstEven = num;
    break;
  }
}

// Podemos usar:
const firstEvenModern = [1, 3, 5, 6, 7, 8].find(num => num % 2 === 0);

console.log(firstEvenModern); // 6

Buenas prácticas

  • Usa break cuando encuentres lo que buscas y no necesites seguir iterando.
  • Usa continue cuando quieras omitir ciertos elementos pero continuar con el resto.
  • Considera alternativas funcionales como filter, find, some o every para código más declarativo.
  • Usa etiquetas con moderación, ya que pueden hacer el código más difícil de seguir.
  • Añade comentarios cuando uses break o continue en situaciones complejas para explicar la lógica.

Las instrucciones break y continue son herramientas poderosas que, cuando se usan correctamente, pueden hacer que tus bucles sean más eficientes y tu código más limpio y expresivo.

Aprende TypeScript online

Otros ejercicios de programación de TypeScript

Evalúa tus conocimientos de esta lección Control de flujo con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Funciones

TypeScript
Test

Reto composición de funciones

TypeScript
Código

Reto tipos especiales

TypeScript
Código

Reto tipos genéricos

TypeScript
Código

Módulos

TypeScript
Test

Polimorfismo

TypeScript
Código

Funciones TypeScript

TypeScript
Código

Interfaces

TypeScript
Puzzle

Funciones puras

TypeScript
Puzzle

Reto namespaces

TypeScript
Código

Funciones flecha

TypeScript
Puzzle

Polimorfismo

TypeScript
Test

Operadores

TypeScript
Test

Conversor de unidades

TypeScript
Proyecto

Funciones flecha

TypeScript
Test

Control de flujo

TypeScript
Código

Herencia

TypeScript
Puzzle

Clases

TypeScript
Puzzle

Proyecto validación de tipado

TypeScript
Proyecto

Clases y objetos

TypeScript
Código

Encapsulación

TypeScript
Test

Herencia

TypeScript
Test

Proyecto sistema de votación

TypeScript
Proyecto

Reto genéricos con clases

TypeScript
Código

Inmutabilidad

TypeScript
Puzzle

Interfaces

TypeScript
Test

Funciones de alto orden

TypeScript
Test

Reto map y filter

TypeScript
Código

Control de flujo

TypeScript
Test

Interfaces

TypeScript
Código

Reto funciones orden superior

TypeScript
Código

Herencia y clases abstractas

TypeScript
Código

Reto tipos mapped

TypeScript
Código

Herencia de clases

TypeScript
Código

Reto funciones puras

TypeScript
Código

Variables y constantes

TypeScript
Puzzle

Introducción a TypeScript

TypeScript
Test

Reto testing unitario

TypeScript
Código

Funciones de primera clase

TypeScript
Puzzle

Clases

TypeScript
Test

OOP y CRUD en TypeScript

TypeScript
Proyecto

Interfaces y su implementación

TypeScript
Código

Tipos genéricos

TypeScript
Test

Namespaces

TypeScript
Test

Operadores y expresiones

TypeScript
Código

Proyecto generador de contraseñas

TypeScript
Proyecto

Reto unión e intersección

TypeScript
Código

Encapsulación

TypeScript
Puzzle

Tipos de unión e intersección

TypeScript
Test

Tipos de unión e intersección

TypeScript
Puzzle

Reto hola mundo en TS

TypeScript
Código

Variables y constantes

TypeScript
Código

Funciones puras

TypeScript
Test

Control de flujo

TypeScript
Código

Introducción a TypeScript

TypeScript
Código

Resolución de módulos

TypeScript
Test

Control de flujo

TypeScript
Puzzle

Reto tipos de utilidad

TypeScript
Código

Reto tipos literales y condicionales

TypeScript
Código

Reto exportar e importar

TypeScript
Código

Propiedades y métodos

TypeScript
Código

Tipos de utilidad

TypeScript
Test

Clases y objetos

TypeScript
Código

Tipos de datos, variables y constantes

TypeScript
Código

Proyecto Minigestor de tareas

TypeScript
Proyecto

Operadores

TypeScript
Puzzle

Funciones flecha y contexto

TypeScript
Código

Proyecto Inventario de productos

TypeScript
Proyecto

Funciones

TypeScript
Puzzle

Reto type aliases

TypeScript
Código

Funciones de alto orden

TypeScript
Puzzle

Funciones y parámetros tipados

TypeScript
Código

Tipos literales

TypeScript
Puzzle

Reto enums

TypeScript
Código

Tipos de utilidad

TypeScript
Puzzle

Modificadores de acceso y encapsulación

TypeScript
Código

Polimorfismo

TypeScript
Puzzle

Tipos genéricos

TypeScript
Puzzle

Reto módulos

TypeScript
Código

Tipos literales

TypeScript
Test

Inmutabilidad

TypeScript
Test

Proyecto Generator de datos

TypeScript
Proyecto

Variables y constantes

TypeScript
Test

Funciones de primera clase

TypeScript
Test

Todas las lecciones de TypeScript

Accede a todas las lecciones de TypeScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Typescript

TypeScript

Introducción Y Entorno

Instalación Y Configuración De Typescript

TypeScript

Introducción Y Entorno

Tipos De Datos, Variables Y Constantes

TypeScript

Sintaxis

Operadores Y Expresiones

TypeScript

Sintaxis

Control De Flujo

TypeScript

Sintaxis

Funciones Y Parámetros Tipados

TypeScript

Sintaxis

Funciones Flecha Y Contexto

TypeScript

Sintaxis

Enums

TypeScript

Sintaxis

Type Aliases Y Aserciones De Tipo

TypeScript

Sintaxis

Clases Y Objetos

TypeScript

Programación Orientada A Objetos

Interfaces Y Su Implementación

TypeScript

Programación Orientada A Objetos

Modificadores De Acceso Y Encapsulación

TypeScript

Programación Orientada A Objetos

Herencia Y Clases Abstractas

TypeScript

Programación Orientada A Objetos

Polimorfismo

TypeScript

Programación Orientada A Objetos

Decoradores Básicos

TypeScript

Programación Orientada A Objetos

Propiedades Y Métodos

TypeScript

Programación Orientada A Objetos

Inmutabilidad

TypeScript

Programación Funcional

Funciones Puras Y Efectos Secundarios

TypeScript

Programación Funcional

Funciones De Primera Clase

TypeScript

Programación Funcional

Funciones De Alto Orden

TypeScript

Programación Funcional

Conceptos Básicos E Inmutabilidad

TypeScript

Programación Funcional

Funciones De Primera Clase Y Orden Superior

TypeScript

Programación Funcional

Composición De Funciones

TypeScript

Programación Funcional

Métodos Funcionales De Arrays (Map, Filter, Reduce)

TypeScript

Programación Funcional

Tipos Literales Y Tipos Condicionales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Genéricos Básicos

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Unión E Intersección

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Utilidad (Partial, Required, Pick, Etc)

TypeScript

Tipos Intermedios Y Avanzados

Unknown, Never Y Tipos Especiales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Mapped

TypeScript

Tipos Intermedios Y Avanzados

Genéricos Con Clases E Interfaces

TypeScript

Tipos Intermedios Y Avanzados

Módulos

TypeScript

Namespaces Y Módulos

Namespaces

TypeScript

Namespaces Y Módulos

Resolución De Módulos

TypeScript

Namespaces Y Módulos

Exportación E Importación De Módulos

TypeScript

Namespaces Y Módulos

Introducción A Módulos

TypeScript

Namespaces Y Módulos

Testing Unitario En Typescript

TypeScript

Testing

Accede GRATIS a TypeScript y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender cómo usar la declaración if para ejecutar código basado en condiciones.
  • Aprender a utilizar la declaración if...else para ejecutar diferentes bloques de código según una condición.
  • Conocer la estructura de la declaración switch para ejecutar diferentes acciones en base a distintas condiciones.
  • Entender cómo funcionan los bucles for, while y do...while para repetir código un número específico de veces o hasta que se cumpla una condición.
  • Saber cómo utilizar el bucle for para iterar sobre una secuencia de valores.
  • Conocer la diferencia entre el bucle while y el bucle do...while.