fromFetch()
📘 RxJS Official Documentation - fromFetch
fromFetch() is a Creation Function for handling HTTP communication as Observable based on the modern Fetch API. It is lightweight compared to ajax() and conforms to modern web standards.
Basic Usage
Simple GET Request
The simplest example of using fromFetch() is to pass a URL and parse the response manually.
import { of, switchMap, catchError, throwError } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
const data$ = fromFetch('https://jsonplaceholder.typicode.com/todos/1').pipe(
switchMap(response => {
if (response.ok) {
// If response is successful, parse JSON
return response.json();
} else {
// If HTTP error, throw error
return throwError(() => new Error(`HTTP Error: ${response.status}`));
}
}),
catchError(error => {
console.error('Error:', error);
return of({ error: true, message: error.message });
})
);
data$.subscribe({
next: data => console.log('Data:', data),
error: error => console.error('Subscription error:', error),
complete: () => console.log('Complete')
});
// Output:
// Data: { userId: 1, id: 1, title: "delectus aut autem", completed: false }
// CompleteIMPORTANT
Important Difference from ajax()
fromFetch()does not callerrorcallback on HTTP errors (4xx, 5xx)- The
okproperty of the response must be checked manually - Parsing operations such as
.json()are also done manually
Usage by HTTP Method
GET Request
import { switchMap } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
interface User {
id: number;
name: string;
email: string;
}
const users$ = fromFetch('https://jsonplaceholder.typicode.com/users').pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json() as Promise<User[]>;
})
);
users$.subscribe({
next: users => console.log('User list:', users),
error: error => console.error('Error:', error)
});POST Request
import { switchMap } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
interface CreateUserRequest {
name: string;
email: string;
}
interface CreateUserResponse {
id: number;
name: string;
email: string;
createdAt: string;
}
const newUser: CreateUserRequest = {
name: 'Taro Yamada',
email: 'taro@example.com'
};
const createUser$ = fromFetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify(newUser)
}).pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json() as Promise<CreateUserResponse>;
})
);
createUser$.subscribe({
next: user => console.log('Creation succeeded:', user),
error: error => console.error('Creation failed:', error)
});PUT Request
import { switchMap } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
interface UpdateUserRequest {
name: string;
email: string;
}
const updatedUser: UpdateUserRequest = {
name: 'Jiro Tanaka',
email: 'jiro@example.com'
};
const updateUser$ = fromFetch('https://api.example.com/users/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedUser)
}).pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
})
);
updateUser$.subscribe({
next: user => console.log('Update succeeded:', user),
error: error => console.error('Update failed:', error)
});DELETE Request
import { switchMap, of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
const deleteUser$ = fromFetch('https://api.example.com/users/1', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer token123'
}
}).pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
// DELETE usually returns empty response or status only
return response.status === 204 ? of(null) : response.json();
})
);
deleteUser$.subscribe({
next: result => console.log('Deletion succeeded:', result),
error: error => console.error('Deletion failed:', error)
});Practical Patterns
Generic HTTP Error Handling Function
Since fromFetch() requires manual error checking, it is useful to create a generic function.
import { Observable, switchMap } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
function fetchJSON<T>(url: string, options?: RequestInit): Observable<T> {
return fromFetch(url, options).pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error ${response.status}: ${response.statusText}`);
}
return response.json() as Promise<T>;
})
);
}
// Usage example
interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
const todo$ = fetchJSON<Todo>('https://jsonplaceholder.typicode.com/todos/1');
todo$.subscribe({
next: todo => console.log('Todo:', todo),
error: error => console.error('Error:', error)
});Detailed Processing by HTTP Status Code
import { throwError, switchMap, of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
const api$ = fromFetch('https://api.example.com/data').pipe(
switchMap(response => {
switch (response.status) {
case 200:
return response.json();
case 204:
// No Content - empty response
return of(null);
case 401:
throw new Error('Authentication required');
case 403:
throw new Error('Access denied');
case 404:
throw new Error('Resource not found');
case 500:
throw new Error('Server error occurred');
default:
throw new Error(`Unexpected HTTP status: ${response.status}`);
}
})
);
api$.subscribe({
next: data => console.log('Data:', data),
error: error => console.error('Error:', error)
});Timeout and Retry
import { switchMap, timeout, retry } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
const api$ = fromFetch('https://api.example.com/slow-endpoint').pipe(
timeout(5000), // Timeout in 5 seconds
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
}),
retry(2) // Retry twice on failure
);
api$.subscribe({
next: data => console.log('Data:', data),
error: error => console.error('Error:', error)
});Request Cancellation (AbortController)
fromFetch() supports canceling requests using the Fetch API's AbortController.
import { switchMap } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
const controller = new AbortController();
const signal = controller.signal;
const api$ = fromFetch('https://api.example.com/data', {
signal // Pass AbortController signal
}).pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
})
);
const subscription = api$.subscribe({
next: data => console.log('Data:', data),
error: error => console.error('Error:', error)
});
// Cancel request after 3 seconds
setTimeout(() => {
controller.abort();
// Or subscription.unsubscribe();
}, 3000);TIP
Automatic Cancellation by RxJS
Just call unsubscribe() and RxJS will cancel the request internally using AbortController. There is no need to manually set up an AbortController.
Searching Based on User Input (switchMap)
import { fromEvent, map, debounceTime, distinctUntilChanged, switchMap, of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
interface SearchResult {
id: number;
title: string;
}
const searchInput = document.querySelector('#search') as HTMLInputElement;
const search$ = fromEvent(searchInput, 'input').pipe(
map(event => (event.target as HTMLInputElement).value),
debounceTime(300),
distinctUntilChanged(),
switchMap(query => {
if (query.length === 0) {
return of([]);
}
return fromFetch(`https://api.example.com/search?q=${encodeURIComponent(query)}`).pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json() as Promise<SearchResult[]>;
})
);
})
);
search$.subscribe({
next: results => console.log('Search results:', results),
error: error => console.error('Search error:', error)
});Execute Multiple Requests in Parallel
import { forkJoin, switchMap } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
interface User {
id: number;
name: string;
}
interface Post {
id: number;
title: string;
}
const users$ = fromFetch('https://jsonplaceholder.typicode.com/users').pipe(
switchMap(response => response.json() as Promise<User[]>)
);
const posts$ = fromFetch('https://jsonplaceholder.typicode.com/posts').pipe(
switchMap(response => response.json() as Promise<Post[]>)
);
forkJoin({
users: users$,
posts: posts$
}).subscribe({
next: ({ users, posts }) => {
console.log('Users:', users);
console.log('Posts:', posts);
},
error: error => console.error('Any request failed:', error)
});Common Use Cases
1. Request with Authentication Token
import { switchMap, Observable } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
function getAuthToken(): string {
return localStorage.getItem('authToken') || '';
}
function fetchWithAuth<T>(url: string, options: RequestInit = {}): Observable<T> {
return fromFetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${getAuthToken()}`,
'Content-Type': 'application/json'
}
}).pipe(
switchMap(response => {
if (response.status === 401) {
throw new Error('Authentication required. Please log in again.');
}
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json() as Promise<T>;
})
);
}
// Usage example
interface UserProfile {
id: number;
name: string;
email: string;
}
const profile$ = fetchWithAuth<UserProfile>('https://api.example.com/profile');
profile$.subscribe({
next: profile => console.log('Profile:', profile),
error: error => console.error('Error:', error)
});2. File Download (Blob)
import { switchMap } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
const downloadFile$ = fromFetch('https://api.example.com/files/report.pdf').pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
// Retrieve as Blob
return response.blob();
})
);
downloadFile$.subscribe({
next: blob => {
// Generate download link from Blob
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'report.pdf';
a.click();
window.URL.revokeObjectURL(url);
console.log('Download complete');
},
error: error => console.error('Download error:', error)
});3. GraphQL Query
import { switchMap, map } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
interface GraphQLResponse<T> {
data?: T;
errors?: Array<{ message: string }>;
}
interface User {
id: string;
name: string;
email: string;
}
function graphqlQuery<T>(query: string, variables?: any): Observable<T> {
return fromFetch('https://api.example.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query, variables })
}).pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json() as Promise<GraphQLResponse<T>>;
}),
map(result => {
if (result.errors) {
throw new Error(result.errors.map(e => e.message).join(', '));
}
if (!result.data) {
throw new Error('No data returned');
}
return result.data;
})
);
}
// Usage example
const query = `
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
const user$ = graphqlQuery<{ user: User }>(query, { id: '1' });
user$.subscribe({
next: ({ user }) => console.log('User:', user),
error: error => console.error('Error:', error)
});4. API with Pagination
import { expand, takeWhile, reduce, switchMap, Observable } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
interface PaginatedResponse<T> {
data: T[];
page: number;
totalPages: number;
}
function fetchAllPages<T>(baseUrl: string): Observable<T[]> {
return fromFetch(`${baseUrl}?page=1`).pipe(
switchMap(response => response.json() as Promise<PaginatedResponse<T>>),
expand(response =>
response.page < response.totalPages
? fromFetch(`${baseUrl}?page=${response.page + 1}`).pipe(
switchMap(res => res.json() as Promise<PaginatedResponse<T>>)
)
: []
),
takeWhile(response => response.page <= response.totalPages, true),
reduce((acc, response) => [...acc, ...response.data], [] as T[])
);
}
// Usage example
interface Item {
id: number;
name: string;
}
const allItems$ = fetchAllPages<Item>('https://api.example.com/items');
allItems$.subscribe({
next: items => console.log('All items:', items),
error: error => console.error('Error:', error)
});fromFetch() Options
fromFetch() can use the RequestInit option of the Fetch API without modification.
interface RequestInit {
method?: string; // HTTP method (GET, POST, PUT, DELETE, etc.)
headers?: HeadersInit; // Request header
body?: BodyInit | null; // Request body
mode?: RequestMode; // cors, no-cors, same-origin
credentials?: RequestCredentials; // omit, same-origin, include
cache?: RequestCache; // Cache mode
redirect?: RequestRedirect; // Redirect processing
referrer?: string; // Referrer
integrity?: string; // Subresource integrity
signal?: AbortSignal; // AbortController signal
}ajax() vs fromFetch() Comparison
| Feature | ajax() | fromFetch() |
|---|---|---|
| Base Technology | XMLHttpRequest | Fetch API |
| Automatic JSON parsing | ✅ getJSON() | ❌ Manually .json() |
| Automatic HTTP error detection | ✅ Automatic error in 4xx/5xx | ❌ Manual response.ok check |
| Progress monitoring | ✅ | ❌ |
| Timeout | ✅ Built-in | ❌ Implemented with RxJS timeout() |
| Request cancellation | ✅ unsubscribe() | ✅ unsubscribe() or AbortController |
| IE11 support | ✅ | ❌ polyfill required |
| Bundle size | Slightly larger | Smaller |
| Service Worker support | ❌ | ✅ |
TIP
How to Choose
- Modern browsers only:
fromFetch()recommended - Need legacy browser support: Use
ajax() - Progress monitoring required: Use
ajax() - Lightweight HTTP communication:
fromFetch()is best - Use in Service Worker: Only
fromFetch()supported
Common Errors and Workarounds
1. HTTP Error Not Caught in error Callback
Problem:
// ❌ next is called even on 404 error
fromFetch('https://api.example.com/not-found').subscribe({
next: response => console.log('Success:', response), // ← Called even on 404
error: error => console.error('Error:', error)
});Solution:
// ✅ Manually check response.ok
fromFetch('https://api.example.com/not-found').pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
})
).subscribe({
next: data => console.log('Data:', data),
error: error => console.error('Error:', error) // ← This is called
});2. CORS Error
Solutions:
- Set CORS headers on server side
- Explicitly specify
mode: 'cors' - Use proxy server during development
fromFetch('https://api.example.com/data', {
mode: 'cors',
credentials: 'include' // If including cookies
});3. Implementing Timeout
Fetch API does not have timeout functionality, so use RxJS timeout().
import { timeout, switchMap } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
const api$ = fromFetch('https://api.example.com/slow').pipe(
timeout(5000), // Timeout in 5 seconds
switchMap(response => response.json())
);Best Practices
1. Create Generic fetchJSON Function
function fetchJSON<T>(url: string, options?: RequestInit): Observable<T> {
return fromFetch(url, options).pipe(
switchMap(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json() as Promise<T>;
})
);
}2. Utilize TypeScript Types
// ✅ Good example: Explicitly specify type
interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
const todo$ = fetchJSON<Todo>('https://jsonplaceholder.typicode.com/todos/1');
// ❌ Bad example: No type specified
const todo$ = fromFetch('https://jsonplaceholder.typicode.com/todos/1')
.pipe(switchMap(res => res.json()));3. Always Implement Error Handling
// ✅ Good example: response.ok and catchError
const api$ = fromFetch('/api/data').pipe(
switchMap(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}),
catchError(error => {
console.error('Error:', error);
return of(defaultValue);
})
);4. Remember to Unsubscribe
// ✅ Good example: Automatic release with takeUntil
class MyComponent {
private destroy$ = new Subject<void>();
ngOnInit() {
fromFetch('/api/data')
.pipe(
switchMap(res => res.json()),
takeUntil(this.destroy$)
)
.subscribe(...);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}Summary
fromFetch() is a lightweight Creation Function for HTTP communication based on the modern Fetch API.
Key Features:
- Based on Fetch API and compliant with the latest web standards
- Lightweight and small bundle size
- Can be used within a Service Worker
- Manual error checking and response parsing required
Usage Scenarios:
- When supporting only modern browsers
- When bundle size needs to be reduced
- When HTTP communication is performed within a Service Worker
- When you want to use Fetch API functions (e.g., Request/Response objects) directly
Important Notes:
- The
errorcallback is not called on HTTP errors (checkresponse.okmanually) - JSON parsing is done manually (
response.json()) - Progress monitoring is not supported
- polyfill is required for legacy browsers such as IE11
Recommended Usage:
- Create a generic
fetchJSON()function for reuse - Ensure type safety by utilizing TypeScript types
- Always implement error handling
- Always unsubscribe when no longer needed
Related Pages
- ajax() - XMLHttpRequest based HTTP communication
- HTTP Communication Creation Functions - ajax() vs. fromFetch()
- switchMap() - Useful operator for canceling HTTP communication
- Error Handling Strategies - Error handling patterns for HTTP communication