Tipos de programadores y cómo utilizarlos
RxJS proporciona varios planificadores para diferentes aplicaciones. Cada planificador tiene su propio tiempo de ejecución y características específicas y puede ser utilizado adecuadamente para optimizar el rendimiento y el comportamiento de la aplicación.
Clasificación de los planificadores
Los planificadores de RxJS se dividen en tres categorías principales.
- Macrotareas: ejecutadas en la siguiente cola de tareas en el bucle de eventos
- Micro-tareas: ejecutadas inmediatamente después de que la tarea actual se complete y antes de que comience la siguiente tarea 3.Procesamiento síncrono: ejecución inmediata
Para más información, consulte Conceptos básicos de tareas y programadores.
Planificadores principales.
asyncScheduler
Características.
- Implementación interna**: utiliza setTimeout.
- Tiempo de ejecución: macro tareas.
- Uso: Procesamiento asíncrono general, procesamiento time-lapse.
import { of, asyncScheduler } from 'rxjs';
import { observeOn } from 'rxjs';
console.log('1: Inicio');
of('Procesamiento asíncrono')
.pipe(observeOn(asyncScheduler))
.subscribe(value => console.log(`3: ${value}`));
console.log('2: Fin');
// Salida:
// 1: Inicio
// 2: Fin
// 3: Procesamiento asíncronoCaso de uso
import { asyncScheduler, map, observeOn, of } from "rxjs";
function heavyComputation(value: number): number {
// Simular cálculo pesado
let result = value;
for (let i = 0; i < 1000000; i++) {
result = Math.sin(result);
}
return result;
}
of(1, 2, 3)
.pipe(
observeOn(asyncScheduler),
map(value => heavyComputation(value))
)
.subscribe(result => {
console.log(`Resultados del cálculo: ${result}`);
});queueScheduler
Características.
- Implementación interna**: micro cola de tareas
- Tiempo de ejecución: dentro de la tarea actual (parece síncrono)
- Usos**: cola de tareas, optimización de la recursividad
import { of, queueScheduler } from 'rxjs';
import { observeOn } from 'rxjs';
console.log('1: Inicio');
of('Procesamiento en cola')
.pipe(observeOn(queueScheduler))
.subscribe(value => console.log(`2: ${value}`));
console.log('3: Fin');
// Salida:
// 1: Inicio
// 2: Procesamiento en cola
// 3: FinCaso de uso
import { Observable, of, queueScheduler } from 'rxjs';
import { observeOn, expand, take, map } from 'rxjs';
// Optimización del procesamiento recursivo
function fibonacci(n: number): Observable<number> {
return of([0, 1]).pipe(
observeOn(queueScheduler),
expand(([a, b]) => of([b, a + b])),
map(([a]) => a),
take(n)
);
}
fibonacci(10).subscribe(value => console.log(value));asapScheduler
Características.
- Implementación interna: Promise.resolve().then() o setImmediate
- Tiempo de ejecución: microtareas
- Uso: Para una ejecución asíncrona lo más rápida posible
import { of, asapScheduler } from 'rxjs';
import { observeOn } from 'rxjs';
console.log('1: Inicio');
of('ASAPProcesamiento')
.pipe(observeOn(asapScheduler))
.subscribe(value => console.log(`3: ${value}`));
console.log('2: Fin');
// Salida:
// 1: Inicio
// 2: Fin
// 3: ASAPProcesamientoCaso de uso
import { fromEvent, asapScheduler } from 'rxjs';
import { observeOn, map } from 'rxjs';
// Optimización de los movimientos del ratón
fromEvent(document, 'mousemove')
.pipe(
observeOn(asapScheduler),
map(event => ({
x: (event as MouseEvent).clientX,
y: (event as MouseEvent).clientY
}))
)
.subscribe(position => {
// UIProcesamiento de actualización de
updateCursor(position);
});animationFrameScheduler
Características.
- Implementación interna**: requestAnimationFrame
- Tiempo de ejecución: antes del siguiente renderizado de pantalla
- Uso: Animación, proceso de dibujo para 60fps.
Ejemplo de una simple animación de rotación
import { animationFrameScheduler, interval } from 'rxjs';
import { take, map } from 'rxjs';
// HTMLCreación de elementos
const box = document.createElement('div');
box.style.width = '100px';
box.style.height = '100px';
box.style.backgroundColor = 'blue';
box.style.position = 'absolute';
box.style.top = '100px';
box.style.left = '100px';
document.body.appendChild(box);
// Creación de animaciones
let rotation = 0;
// 60fpsEn los2Segundos de animación
interval(0, animationFrameScheduler)
.pipe(
take(120), // 60fps × 2Segundos = 120Fotograma
map(() => {
rotation += 3; // 1Cada fotograma3Grado de rotación
return rotation;
})
)
.subscribe(angle => {
// DOMRotación real del elemento
box.style.transform = `rotate(${angle}deg)`;
});¿Por qué necesitamos animationFrameScheduler?
El animationFrameScheduler realiza sus operaciones de forma sincronizada con el ciclo de dibujo del navegador, lo que tiene las siguientes ventajas.
1.Animaciones suaves: como el procesamiento está sincronizado con el tiempo de renderizado del navegador (normalmente 60 fps), se consiguen animaciones suaves y sin cortes. 2.Uso eficiente de los recursos: cuando el navegador desactiva la pestaña, la ejecución del requestAnimationFrame se pausa automáticamente, evitando el uso innecesario de la CPU. 3, Prevención del parpadeo de la pantalla: se evita el parpadeo de la pantalla y los fotogramas incompletos porque el cálculo se completa de forma fiable antes de que se dibuje la pantalla.
He aquí una comparación entre setInterval y animationFrameScheduler.
import { animationFrameScheduler, interval, map } from "rxjs";
// ❌ setIntervalAnimación ineficiente utilizando
let position = 0;
const intervalId = setInterval(() => {
position += 1;
element.style.transform = `translateX(${position}px)`;
}, 16); // aproximadamente60fps
// Problemas:
// - No se sincroniza con los tiempos de renderizado del navegador
// - Continúa ejecutándose incluso en pestañas en segundo plano
// - No se puede garantizar la precisión de60fpsno se puede garantizar
// ✅ animationFrameSchedulerUso eficiente de la animación
interval(0, animationFrameScheduler)
.pipe(
map(() => {
position += 1;
return position;
})
)
.subscribe(pos => {
element.style.transform = `translateX(${pos}px)`;
});
// Ventajas
// - Sincronizado con los tiempos de dibujo del navegador
// - Pausa automática en las pestañas de fondo
// - Estable60fpsPermiteEjemplo de animación siguiendo al ratón
import { fromEvent, animationFrameScheduler, interval } from 'rxjs';
import { withLatestFrom, observeOn, map } from 'rxjs';
// Creación de un círculo a seguir
const circle = document.createElement('div');
circle.style.width = '30px';
circle.style.height = '30px';
circle.style.borderRadius = '50%';
circle.style.backgroundColor = 'red';
circle.style.position = 'fixed';
circle.style.pointerEvents = 'none'; // Transparente a los eventos del ratón
document.body.appendChild(circle);
// Posición actual y de destino
let currentX = 0;
let currentY = 0;
let targetX = 0;
let targetY = 0;
// Monitoriza los eventos de movimiento del ratón
const mouseMove$ = fromEvent<MouseEvent>(document, 'mousemove')
.pipe(
map(event => ({
x: event.clientX,
y: event.clientY
}))
);
// Bucle de animación
interval(0, animationFrameScheduler)
.pipe(
withLatestFrom(mouseMove$),
map(([_, mousePos]) => mousePos)
)
.subscribe(({ x, y }) => {
// Establecer la posición del ratón como objetivo
targetX = x;
targetY = y;
// Movimiento gradual desde la posición actual hacia la posición de destino々Movimiento gradual (easing) desde la posición actual hacia la posición objetivo
currentX += (targetX - currentX) * 0.1;
currentY += (targetY - currentY) * 0.1;
// DOMActualizar elemento
circle.style.left = `${currentX - 15}px`; // Ajuste a la posición central
circle.style.top = `${currentY - 15}px`;
});Guía para el uso de diferentes programadores
Comparación por tiempo de ejecución
import { of, asyncScheduler } from 'rxjs';
import { observeOn } from 'rxjs';
console.log('1: Inicio');
of('Procesamiento asíncrono')
.pipe(observeOn(asyncScheduler))
.subscribe(value => console.log(`3: ${value}`));
console.log('2: Fin');
// Salida:
// 1: Inicio
// 2: Fin
// 3: Procesamiento asíncronoCriterios de selección por aplicación
Casos prácticos de uso
Procesamiento de grandes cantidades de datos
Procesamiento de mensajes WebSocket.
import { webSocket } from 'rxjs/webSocket';
import { asapScheduler } from 'rxjs';
import { observeOn } from 'rxjs';
// 注: これは概念を示す疑似コードです
const socket$ = webSocket<any>({
url: 'wss://your-websocket-server.com',
deserializer: msg => msg.data // 文字列として扱う
});
socket$
.pipe(
// 高速な応答が必要なメッセージ処理
observeOn(asapScheduler)
)
.subscribe(message => {
handleMessage(message);
});
function handleMessage(msg: any) {
console.log('メッセージ受信:', msg);
}Utilización del programador con retardo exponencial
Como control más avanzado, el backoff exponencial puede implementarse combinando retryWhen y asyncScheduler.
import { throwError, timer, of } from 'rxjs';
import { retry, mergeMap } from 'rxjs';
function fetchDataWithBackoff(id: number) {
return of(id).pipe(
mergeMap(() => {
const random = Math.random();
if (random > 0.9) {
return of({ id, data: 'success' });
}
return throwError(() => new Error('Temporary error'));
})
);
}
fetchDataWithBackoff(1)
.pipe(
// RxJS 7.3+ 推奨: retry({ count, delay }) 形式
retry({
count: 3, // Máx.3回まで再試行
delay: (error, retryCount) => {
// 指数バックオフ: 1秒, 2秒, 4秒...
const delayTime = Math.pow(2, retryCount - 1) * 1000;
console.log(`🔄 リトライ ${retryCount}回目 (${delayTime}ms後)`);
// timer は内部的に asyncScheduler を使用
return timer(delayTime);
}
})
)
.subscribe({
next: result => console.log('✅ 成功:', result),
error: error => {
console.log('❌ Máx.リトライ数に到達');
console.log('❌ 最終Error:', error.message);
}
});
// SalidaEj.:
// 🔄 リトライ 1回目 (1000ms後)
// 🔄 リトライ 2回目 (2000ms後)
// 🔄 リトライ 3回目 (4000ms後)
// ❌ Máx.リトライ数に到達
// ❌ 最終Error: Temporary errorCuando se especifica explícitamente un Scheduler asíncrono
Especificar explícitamente un Scheduler específico permite un control más flexible, como sustituirlo por TestScheduler durante las pruebas.
import { of, asyncScheduler } from 'rxjs';
import { observeOn } from 'rxjs';
console.log('1: Inicio');
of('Procesamiento asíncrono')
.pipe(observeOn(asyncScheduler))
.subscribe(value => console.log(`3: ${value}`));
console.log('2: Fin');
// Salida:
// 1: Inicio
// 2: Fin
// 3: Procesamiento asíncrono
- En la página retry y catchError se describen patrones de implementación detallados y métodos de depuración para el manejo de retry.
- Uso detallado del operador retry.
- Patrones de combinación con catchError.
- Técnicas de depuración para reintentos (por ejemplo, seguimiento del número de intentos, registro, etc.).
Impacto en el rendimiento.
Sobrecarga del programador
import { of, asyncScheduler } from 'rxjs';
import { observeOn } from 'rxjs';
console.log('1: Inicio');
of('Procesamiento asíncrono')
.pipe(observeOn(asyncScheduler))
.subscribe(value => console.log(`3: ${value}`));
console.log('2: Fin');
// Salida:
// 1: Inicio
// 2: Fin
// 3: Procesamiento asíncronoResumen.
La elección del planificador tiene un impacto significativo en el rendimiento y la capacidad de respuesta de una aplicación. Comprender las características de cada planificador y utilizarlos en las situaciones adecuadas garantizará un funcionamiento eficiente y sin problemas. Como pauta general,
- Scheduler` para el procesamiento asíncrono en general.
- Scheduler para el procesamiento recursivo y las colas síncronas.
- Scheduler` para tiempos de respuesta rápidos.
animationFrameSchedulerpara animación
para animación.