From 19a818cd8bdbb4728fa131d22fb1c56742511fcb Mon Sep 17 00:00:00 2001 From: Dani Lipari Date: Sat, 10 Feb 2024 15:38:54 +0100 Subject: [PATCH] feat(delay-operator): support long delays beyond 24.9 days by segmenting the delay This commit updates the delay operator to handle delays longer than 2147483647 ms (~24.9 days), addressing the JavaScript timer limitation. It introduces a segmented delay approach, allowing the delay operator to accurately manage long delay periods without exceeding the maximum setTimeout/setInterval limit. This change ensures reliable delay operations for scenarios requiring long wait times, such as token expiration or delayed notifications, improving the operator's versatility and reliability. --- packages/rxjs/src/internal/operators/delay.ts | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/rxjs/src/internal/operators/delay.ts b/packages/rxjs/src/internal/operators/delay.ts index b1608c2abd..688df81cf4 100644 --- a/packages/rxjs/src/internal/operators/delay.ts +++ b/packages/rxjs/src/internal/operators/delay.ts @@ -1,23 +1,25 @@ -import { asyncScheduler } from '../scheduler/async.js'; -import type { MonoTypeOperatorFunction, SchedulerLike } from '../types.js'; -import { delayWhen } from './delayWhen.js'; -import { timer } from '../observable/timer.js'; +import { asyncScheduler } from '../scheduler/async'; +import { MonoTypeOperatorFunction, SchedulerLike } from '../types'; +import { delayWhen } from './delayWhen'; +import { timer } from '../observable/timer'; +import { Observable } from '../Observable'; /** * Delays the emission of items from the source Observable by a given timeout or * until a given Date. * * Time shifts each item by some specified amount of - * milliseconds. + * milliseconds, even for very long periods. * * ![](delay.svg) * * If the delay argument is a Number, this operator time shifts the source * Observable by that amount of time expressed in milliseconds. The relative - * time intervals between the values are preserved. + * time intervals between the values are preserved. For delays longer than 2147483647 ms (~24.9 days), + * the delay is segmented to avoid JavaScript timer limitations. * * If the delay argument is a Date, this operator time shifts the start of the - * Observable execution until the given date occurs. + * Observable execution until the given date occurs. It correctly handles dates far in the future. * * ## Examples * @@ -55,11 +57,27 @@ import { timer } from '../observable/timer.js'; * @param due The delay duration in milliseconds (a `number`) or a `Date` until * which the emission of the source items is delayed. * @param scheduler The {@link SchedulerLike} to use for managing the timers - * that handle the time-shift for each item. + * that handle the time-shift for each item, with support for long delays. * @return A function that returns an Observable that delays the emissions of - * the source Observable by the specified timeout or Date. + * the source Observable by the specified timeout or Date, correctly handling long delays. */ export function delay(due: number | Date, scheduler: SchedulerLike = asyncScheduler): MonoTypeOperatorFunction { - const duration = timer(due, scheduler); - return delayWhen(() => duration); + // Helper function to handle long delays + function delaySegmented(dueTime: number, action: () => void, scheduler: SchedulerLike) { + if (dueTime <= 2_147_483_647) { + scheduler.schedule(action, dueTime); + } else { + // Schedule the first segment up to the maximum limit + scheduler.schedule(() => { + // Calculate the remaining time and apply recursively + const remainingTime = dueTime - 2_147_483_647; + delaySegmented(remainingTime, action, scheduler); + }, 2_147_483_647); + } + } + + const dueTime = due instanceof Date ? due.getTime() - Date.now() : due; + return delayWhen(() => new Observable((subscriber) => { + delaySegmented(dueTime, () => subscriber.complete(), scheduler); + })); }