range() - Genera un rango de números
range() es una Función de Creación similar a sentencia for que emite un número especificado de enteros consecutivos desde un valor inicial especificado.
Resumen
range() emite una secuencia de enteros consecutivos como Observable especificando un valor inicial y el número de enteros. Se usa para generación de números secuenciales y procesamiento por lotes como una forma declarativa de reemplazar la sentencia for tradicional.
Firma:
function range(
start: number,
count?: number,
scheduler?: SchedulerLike
): Observable<number>Parámetros:
start: El valor inicial (desde donde empezar a emitir)count: el número de valores a emitir (omitido, desde 0 hasta menos destart)scheduler: el scheduler para emitir los valores (omitido: emite síncronamente)
Documentación Oficial: 📘 RxJS Oficial: range()
Uso Básico
Patrón 1: Especificar valor inicial y conteo
Este es el uso más común.
import { range } from 'rxjs';
// Generar 5 números secuenciales desde 1 (1, 2, 3, 4, 5)
range(1, 5).subscribe({
next: value => console.log('Valor:', value),
complete: () => console.log('Completado')
});
// Salida:
// Valor: 1
// Valor: 2
// Valor: 3
// Valor: 4
// Valor: 5
// CompletadoPatrón 2: Números secuenciales comenzando desde 0
Estableciendo el valor inicial en 0, se puede generar un número secuencial como un índice de array.
import { range } from 'rxjs';
// 0 a 10 números secuenciales (0, 1, 2, ..., 9)
range(0, 10).subscribe(console.log);
// Salida: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9Patrón 3: Comenzar con un número negativo
También se pueden generar números negativos.
import { range } from 'rxjs';
// 5 números secuenciales desde -3 (-3, -2, -1, 0, 1)
range(-3, 5).subscribe(console.log);
// Salida: -3, -2, -1, 0, 1Características Importantes
1. Emisión Síncrona
Por defecto, range() emite todos los valores síncronamente al suscribirse.
import { range } from 'rxjs';
console.log('Antes de la suscripción');
range(1, 3).subscribe(value => console.log('Valor:', value));
console.log('Después de la suscripción');
// Salida:
// Antes de la suscripción
// Valor: 1
// Valor: 2
// Valor: 3
// Después de la suscripción2. Se Completa Inmediatamente
Notifica complete inmediatamente después de emitir todos los valores.
import { range } from 'rxjs';
range(1, 3).subscribe({
next: val => console.log(val),
complete: () => console.log('¡Completado!')
});
// Salida: 1, 2, 3, ¡Completado!3. Equivalencia con sentencia for
range(start, count) es equivalente a la siguiente sentencia for.
// Sentencia for imperativa
for (let i = start; i < start + count; i++) {
console.log(i);
}
// range() declarativo
range(start, count).subscribe(console.log);Casos de Uso Prácticos
1. Procesamiento por Lotes
Se usa para ejecutar múltiples tareas secuencialmente.
import { range, of, Observable, concatMap, delay, map } from 'rxjs';
// Función para simular procesamiento de datos
function processItem(index: number): Observable<string> {
return of(index).pipe(
delay(100), // Simular 100ms de tiempo de procesamiento
map(i => `Resultado de procesar item ${i}`)
);
}
// Procesar 10 items de datos secuencialmente (1 segundo de retraso entre cada proceso)
range(1, 10).pipe(
concatMap(index =>
processItem(index).pipe(delay(1000))
)
).subscribe({
next: result => console.log(`Procesamiento completo: ${result}`),
complete: () => console.log('Todo el procesamiento completado')
});
// Salida:
// Procesamiento completo: Resultado de procesar item 1 (después de aproximadamente 1.1 segundos)
// Procesamiento completo: Resultado de procesar item 2 (después de aproximadamente 2.1 segundos)
// ...
// Procesamiento completo: Resultado de procesar item 10 (después de aproximadamente 10.1 segundos)
// Todo el procesamiento completado2. Paginación
Obtener múltiples páginas de datos secuencialmente.
import { range, of, Observable, concatMap, delay } from 'rxjs';
interface PageData {
page: number;
items: string[];
}
// Función para simular obtención de datos de página
function fetchPage(page: number): Observable<PageData> {
return of({
page,
items: [`Item${page}-1`, `Item${page}-2`, `Item${page}-3`]
}).pipe(
delay(500) // Simular llamada API
);
}
function fetchAllPages(totalPages: number) {
return range(1, totalPages).pipe(
concatMap(page => fetchPage(page))
);
}
fetchAllPages(5).subscribe({
next: (data: PageData) => console.log(`Página ${data.page}:`, data.items),
complete: () => console.log('Todas las páginas obtenidas')
});
// Salida:
// Página 1: ['Item1-1', 'Item1-2', 'Item1-3']
// Página 2: ['Item2-1', 'Item2-2', 'Item2-3']
// Página 3: ['Item3-1', 'Item3-2', 'Item3-3']
// Página 4: ['Item4-1', 'Item4-2', 'Item4-3']
// Página 5: ['Item5-1', 'Item5-2', 'Item5-3']
// Todas las páginas obtenidas3. Procesamiento de Índices de Array
Usar como bucle basado en índices al procesar cada elemento de un array.
import { range, map } from 'rxjs';
const items = ['Manzana', 'Banana', 'Cereza', 'Dátil', 'Saúco'];
range(0, items.length).pipe(
map(index => ({ index, item: items[index] }))
).subscribe(({ index, item }) => {
console.log(`[${index}] ${item}`);
});
// Salida:
// [0] Manzana
// [1] Banana
// [2] Cereza
// [3] Dátil
// [4] Saúco4. Generación de Datos de Prueba
Esto es útil para generar datos mock para pruebas unitarias.
import { range, map, toArray } from 'rxjs';
// Generar datos de usuario mock
range(1, 100).pipe(
map(id => ({
id,
name: `Usuario${id}`,
email: `usuario${id}@example.com`
})),
toArray()
).subscribe(users => {
console.log(`${users.length} usuarios generados`);
// Usar en pruebas
});5. Contador para Procesamiento de Reintentos
Controla el número de reintentos en caso de error.
import { range, throwError, concat, of, Observable, mergeMap, delay, catchError, map, toArray } from 'rxjs';
// Función para simular obtención de datos (falla aleatoriamente)
function fetchData(): Observable<string> {
const shouldFail = Math.random() > 0.7; // 30% probabilidad de éxito
return of(shouldFail).pipe(
delay(300),
mergeMap(fail =>
fail
? throwError(() => new Error('Obtención de datos fallida'))
: of('Obtención de datos exitosa')
)
);
}
function fetchWithRetry(maxRetries: number = 3) {
return concat(
range(0, maxRetries).pipe(
map(attempt => {
console.log(`Intento ${attempt + 1}/${maxRetries}`);
return fetchData().pipe(
catchError(error => {
if (attempt === maxRetries - 1) {
return throwError(() => new Error('Máximo de reintentos alcanzado'));
}
return throwError(() => error);
}),
delay(Math.pow(2, attempt) * 1000) // Backoff exponencial
);
}),
toArray()
)
);
}
fetchWithRetry().subscribe({
next: result => console.log('Resultado:', result),
error: err => console.error('Error:', err.message)
});
// Ejemplo de salida:
// Intento 1/3
// Intento 2/3
// Resultado: Obtención de datos exitosaAsincronización con Scheduler
Al procesar grandes cantidades de datos, la ejecución asíncrona es posible especificando un scheduler.
import { range, asyncScheduler, observeOn } from 'rxjs';
console.log('Inicio');
// Emitir 1,000,000 números asíncronamente
range(1, 1000000).pipe(
observeOn(asyncScheduler)
).subscribe({
next: val => {
if (val % 100000 === 0) {
console.log(`Progreso: ${val}`);
}
},
complete: () => console.log('Completado')
});
console.log('Después de suscripción (asíncrono, así que se ejecuta inmediatamente)');
// Salida:
// Inicio
// Después de suscripción (asíncrono, así que se ejecutará inmediatamente)
// Progreso: 100000
// Progreso: 200000
// ...
// CompletadoTIP
Uso del Scheduler:
- No bloquear la UI al procesar grandes cantidades de datos
- Control de tiempo en pruebas (TestScheduler)
- Control del event loop en entorno Node.js
Para más información, consulta Tipos de Schedulers y Cómo Usarlos.
Comparación con Otras Funciones de Creación
range() vs of()
import { range, of } from 'rxjs';
// range() - enteros consecutivos
range(1, 3).subscribe(console.log);
// Salida: 1, 2, 3
// of() - enumerar valores arbitrarios
of(1, 2, 3).subscribe(console.log);
// Salida: 1, 2, 3
// Diferencia: range() solo acepta números secuenciales, of() acepta valores arbitrarios
of(1, 10, 100).subscribe(console.log);
// Salida: 1, 10, 100 (no posible con range())range() vs from()
import { range, from } from 'rxjs';
// range() - generar números secuenciales
range(1, 5).subscribe(console.log);
// Salida: 1, 2, 3, 4, 5
// from() - generar desde un array (debe crear array previamente)
from([1, 2, 3, 4, 5]).subscribe(console.log);
// Salida: 1, 2, 3, 4, 5
// Ventaja de range(): no hay pre-asignación de arrays en memoria
range(1, 1000000); // Eficiente en memoria
from(Array.from({ length: 1000000 }, (_, i) => i + 1)); // El array va a memoriarange() vs generate()
import { range, generate } from 'rxjs';
// range() - numeración secuencial simple
range(1, 5).subscribe(console.log);
// Salida: 1, 2, 3, 4, 5
// generate() - un ejemplo complejo de lo mismo
generate(
1, // Valor inicial
x => x <= 5, // Condición de continuación
x => x + 1 // Iteración
).subscribe(console.log);
// Salida: 1, 2, 3, 4, 5
// Ventajas de generate(): condición compleja y gestión de estado
generate(
1,
x => x <= 100,
x => x * 2 // Incrementa por factor de 2
).subscribe(console.log);
// Salida: 1, 2, 4, 8, 16, 32, 64
// (no posible con range())TIP
Criterios de Selección:
- Requiere números secuenciales →
range() - Enumerar cualquier valor →
of() - Array/Promise existente →
from() - Condición/paso complejo →
generate()
Consideraciones de Rendimiento
Debido a que range() emite valores síncronamente, se debe considerar el rendimiento al generar grandes números de valores.
WARNING
Manejo de Grandes Cantidades de Datos:
// ❌ Mal ejemplo: emitir 1 millón de valores síncronamente (la UI se bloqueará)
range(1, 1000000).subscribe(console.log);
// ✅ Buen ejemplo 1: asíncrono con scheduler
range(1, 1000000).pipe(
observeOn(asyncScheduler)
).subscribe(console.log);
// ✅ Buen ejemplo 2: Dividir por buffering
range(1, 1000000).pipe(
bufferCount(1000)
).subscribe(batch => console.log(`${batch.length} casos procesados`));Elegir Entre from() Array
import { range, from } from 'rxjs';
// Si necesitas números secuenciales → range() es más conciso
range(0, 10).subscribe(console.log);
// No hay necesidad de crear un array y luego convertirlo (ineficiente)
from(Array.from({ length: 10 }, (_, i) => i)).subscribe(console.log);
// Si hay un array existente → usar from()
const existingArray = [5, 10, 15, 20];
from(existingArray).subscribe(console.log);Manejo de Errores
Aunque range() en sí no emite errores, pueden ocurrir errores en el pipeline.
import { range, of, map, catchError } from 'rxjs';
range(1, 10).pipe(
map(n => {
if (n === 5) {
throw new Error('Error en 5');
}
return n * 2;
}),
catchError(error => {
console.error('Error ocurrido:', error.message);
return of(-1); // Retornar valor predeterminado
})
).subscribe(console.log);
// Salida: 2, 4, 6, 8, -1Resumen
range() es una Función de Creación simple pero poderosa que produce una secuencia de enteros consecutivos.
IMPORTANT
Características de range():
- ✅ Ideal para generar números consecutivos (alternativa a sentencia for)
- ✅ Útil para procesamiento por lotes, paginación, generación de datos de prueba
- ✅ Eficiente en memoria (no hay pre-creación de arrays)
- ⚠️ Considerar asíncrono para grandes cantidades de datos
- ⚠️ Usar
generate()para condiciones complejas
Temas Relacionados
- generate() - Generación de bucles genérica
- of() - Enumera valores arbitrarios
- from() - Convertir desde array o Promise
- interval() - Emitir valores periódicamente