Wait for a minimum of X milliseconds using RxJS

I'm attempting to achieve a specific functionality using RxJS:

  1. Trigger an event
  2. Make an API call
  3. Upon receiving the API response, do one of the following:
    1. Wait for at least X milliseconds after triggering the event
    2. If X milliseconds have already passed since triggering the event, return immediately

This feature is valuable for enhancing user experience, as it ensures that a loading icon is displayed for a minimum duration even if the API call is swift.

So far, I have not been able to implement this using delay, throttle, debounce, or any similar methods.

this.triggeredEvent
    .switchMap(data => {
        let startTime = Date.now();
        return this.invokeApiService(data)
            .delay(new Date(startTime + 1000));
    })

I initially thought this approach would work, but using an absolute date seems to calculate the time difference from the current time rather than scheduling the delay for that exact time.


UPDATE:

It appears there isn't a pre-built operator that functions according to my requirements. Therefore, I designed a custom solution as I anticipate using it extensively in my project:

import { Observable } from "rxjs/Observable";

function delayAtLeast<T>(this: Observable<T>, delay: number): Observable<T> {
    return Observable.combineLatest(
        Observable.timer(delay),
        this)
    .map(([_, i]) => i);
}

Observable.prototype.delayAtLeast = delayAtLeast;

declare module "rxjs/Observable" {
    interface Observable<T> {
        delayAtLeast: typeof delayAtLeast;
    }
}

Answer №1

When it comes to delaying a process, whether by date or number, the essence remains the same. The only distinguishing factor lies in how the delay duration is determined, which is based on the variance between the specified date and the current time.

If you wish to introduce a delay when a value is emitted, you can utilize the delayWhen operator:

this.eventTrigger
    .switchMap(data => {
        let initialTime = Date.now();
        return this.fetchDataService(data)
            .delayWhen(() => Rx.Observable.timer(500 + initialTime - Date.now()))
    })

Answer №2

What is the issue with your combineLatest approach?

Another option is to utilize zip:

this.eventThatFires
    .switchMap(data => Observable.zip(
        this.profileService.updateInfo(profileInfo)),
        Observable.timer(500),
        x => x));

Answer №3

Creating my own custom rxjs operator inspired by Martin's ultimate solution.

import { combineLatest, Observable, OperatorFunction, timer } from "rxjs";
import { map } from "rxjs/operators";

export function ensureDelay<T>(delay: number): OperatorFunction<T, T> {
  return function(source$: Observable<T>): Observable<T> {
    return combineLatest([timer(delay), source$]).pipe(map(x => x[1]));
  }
}

Answer №4

According to the response from Bogdan Savluk, the delay functionality has been decoupled:

let effectiveDelay=(delay)=>{
    let effectiveTimer = (now=>delay=>timer(delay+now - Date.now() )) (Date.now())
    return delayWhen(()=>effectiveTimer(delay));
}


this.eventThatFires
    .switchMap(data => this.callHttpService(data)
                .pipe(effectiveDelay(500)))

For HTTP requests with a specified interval (polling every x seconds)

of({}).pipe(
    switchMap(data => {
        return ajax({url:"https://httpbin.org/delay/2", crossDomain:true})
              .pipe(effectiveDelay(1000), map(ax=>ax.response) )
    }), tap(display), repeat())
  .subscribe()

Check it out: online

Answer №5

Here is another option:

combineLatest([ this.eventThatFires, timer(500) ]).pipe(
  map(([ result ]) => result)
)

combineLatest will wait for both this.eventThatFires and timer(500) to emit values. It then returns an array, but we only need the first element, so we use map to extract it.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Router-outlet @input decorator experiencing issues with multiple components

Within my router module, I have a route set up for /dashboard. This route includes multiple components: a parent component and several child components. The data I am trying to pass (an array of objects called tablesPanorama) is being sent from the parent ...

Find the distinct values from an array of objects containing varying elements using Typescript

My array contains dynamic elements within objects: [ { "Value1": [ "name", "surname", "age" ], "Value2": [ "name" ...

Using Typescript to define the type for React's useState() setter function whenever

I'm working on setting up a React Context to handle parameters mode and setMode, which act as getter and setter for a React state. This is necessary in order to update the CSS mode (light / dark) from child components. I'm encountering a Typescr ...

Enhance the background property in createMuiTheme of Material-UI by incorporating additional properties using Typescript

I've been attempting to include a new property within createMuiTheme, but Typescript is not allowing me to do so. I followed the instructions provided here: https://next.material-ui.com/guides/typescript/#customization-of-theme I created a .ts file ...

Transform Typescript code into Javascript using webpack while preserving the folder organization

Is there a way for webpack to compile my typescript node project into js while preserving the directory structure and not bundling into one file? This is my current project structure: src |_controllers |_home |_index.ts |_ services ...

The protractor tool is unable to recognize an Angular component when it is displayed on the page

I have implemented end-to-end (e2e) testing on my project, but I am facing issues that are causing my tests to fail. Below is a snippet of my app.po.ts file: import { browser, by, element } from 'protractor'; export class AppPage { public b ...

Patience is key as you wait for the observable to finish

My methods have dependencies where one method needs to complete before the next can be called. process1(data: string) : Observable<string> { this.dataservice.process(data).subscribe( (response) => { return response. ...

What is the process for transitioning global reusable types to package types within turborepo?

When creating an app within the apps folder, a global.d.ts file is required with specific types defined like this: interface Window{ analytics: any; } This file should be designed to be reusable and placed in the packages/types directory for easy acce ...

Tips for utilizing the latest hook feature in Typegoose

After adding a pre hook on updateOne events, I noticed it functions differently compared to save events... I believe this discrepancy is due to the fact that the update command typically includes a matcher as its first argument. I attempted to capture the ...

Demystifying the Mechanics of RxJS Subscriptions during an HTTP Request

export class VendorHttpService { result = '0'; constructor(private http: HttpClient, private global: GlobalService) { } getProfileStatus(uid: String): string { this.http.get(this.global.getUrl()+"/vendor/profile-status/"+uid) ...

Encountering a problem while compiling the Next.js app

Whenever I execute the command npm run build for a Next.js project (built with React and TypeScript), I encounter the following error: Error: Missing "key" prop for element in array react/jsx-key This issue is specifically related to the following piec ...

Issue with importing and exporting external types causing failures in Jest unit tests for Vue 2

I am in the process of creating a package that will contain various types, enums, consts, and interfaces that I frequently use across different projects. To achieve this, I have set up a main.ts file where I have consolidated all the exports and specified ...

Having trouble mocking Node fs Modules using Sinon

I am facing an issue with mocking the promises methods of the node fs module in my code. When my appData.ts file is executed, it calls the actual fs.promises.mkdir method instead of the mock function defined in \__tests__/appData.test.js. I suspect ...

What is the best way to implement multiple templates for a single component?

Is there a way to configure my Home Component based on the user's role? For instance: If the employee is an admin, then the home component should load the template URL designed for admins. Likewise, if the employee is a cashier, then the home compo ...

What is the best way to share type definitions between a frontend and a Golang backend application?

I utilized typescript for both the frontend (Angular) and backend (Express). To ensure type definitions are shared, I created a file called shared-type-file.ts. interface Kid{ name: string; age: number; } By then running npm install in both the front ...

How can I configure Material-UI's textfield to return a numerical value instead of a string when the type is set to "number"?

Utilizing Material-UI's TextField alongside react-hook-form to monitor inputs has raised an issue for me. I have observed that regardless of the input type, it always returns a string. This creates conflicts with the data types I am using in my codeba ...

What is the best way to track events in angular-meteor when a user logs in, logs out, or when there is a change in the user

I am working on meteor-angular and trying to track new user login and logout changes within a single component. I have attempted to subscribe to userData in the component's initialization, but it does not seem to detect when the user logs in or out. I ...

Modifying an onClick handler function within a react element located in a node module, which points to a function in a prop declared in the main Component file

I have successfully implemented the coreui CDataTable to display a table. return ( <CDataTable items={data} fields={fields} ... /> ) Everything is working smoothly, but I wanted to add an extra button in the header of the C ...

Ways to transmit information or notifications from a service to a component

Currently, I am utilizing Angular 6 and have the upload file control on three different screens (three various components). Each of these screens calls the same method UploadFile(). The main issue arises when I need to make any changes to this method, as ...

The Environment variable in React Native is not functioning when utilizing TypeScript

After installing the react-native-dotenv library, I followed the instructions outlined in the TypeScript guide. I then created a .env file with the following contents: MARVEL_API = <url> MARVEL_PUBLIC_KEY = <public-key> MARVEL_PRIVATE_KEY = &l ...