0%

rxjs-function-timeout

Introduction

In RxJS, there is a timeout operator, it’s used to throw an error if the source observable does not emit a value within a specified timeout duration.

Use case

In Angular, the HttpClient service is used to make HTTP requests. Sometimes, we want to set a timeout for the request, if the request does not complete within the specified time, we want to cancel the request and show an error message to the user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { catchError, timeout } from 'rxjs/operators';
import { throwError } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class ApiService {
constructor(private http: HttpClient) {}

getData() {
return this.http.get('https://example.com/api/data').pipe(
timeout(10000), // 10 seconds
catchError((error) => {
// Handle timeout or other errors
console.error('Request timed out or failed', error);
return throwError(error);
})
);
}
}

But, wait, what if I want to add this timeout for all my requests? Do I need to add the timeout operator to every request? The answer is no, you can create an interceptor to add the timeout operator to all requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { timeout } from 'rxjs/operators';

export const DEFAULT_TIMEOUT = new InjectionToken<number>('defaultTimeout');

@Injectable()
export class TimeoutInterceptor implements HttpInterceptor {
constructor(@Inject(DEFAULT_TIMEOUT) protected defaultTimeout: number) {
}

intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(timeout(this.defaultTimeout));
}
}

Don’t forget to add the interceptor to the providers array in the AppModule.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { TimeoutInterceptor } from './timeout.interceptor';

@NgModule({
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: TimeoutInterceptor,
multi: true,
},
{ provide: DEFAULT_TIMEOUT, useValue: 30000 }
],
})
export class AppModule {}

Now, all your HTTP requests will have a timeout of 10 seconds.

But, what if I want to set different timeout values for some specific requests? You can add a custom header to the request and check it in the interceptor, if custom header timeout exists, use the custom timeout value, otherwise use the default timeout value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class TimeoutInterceptor implements HttpInterceptor {
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const timeoutValue = req.headers.get('timeout') || this.defaultTimeout;
const timeoutValueNumeric = Number(timeoutValue);

return next.handle(req).pipe(timeout(timeoutValueNumeric));
}
}

Now, you can set the timeout value in the request headers.

1
2
3
4
import { HttpHeaders } from '@angular/common/http';

const headers = new HttpHeaders().set('timeout', '5000'); // 5 seconds
this.http.get('https://example.com/api/data', { headers });

References

  1. https://rxjs.dev/api/index/function/timeout
  2. https://stackoverflow.com/questions/45938931/default-and-specific-request-timeout