Side Effect on Errors

Avatar of Hemanta SundarayHemanta Sundaray

Sometimes you don’t want to recover from an error. You just want to log it, send it to an error tracking service, increment a metric, then let it continue propagating.

HttpClient.tapError() runs a side effect when an error occurs, without changing the error or preventing it from propagating.

http.ts
import {
FetchHttpClient,
HttpClient,
HttpClientRequest,
} from "effect/unstable/http";
import { Effect } from "effect";
function fetchUserWithErrorTracking(userId: number) {
return Effect.gen(function* () {
const client = (yield* HttpClient.HttpClient).pipe(
HttpClient.filterStatusOk,
HttpClient.tapError((error) =>
Effect.sync(() => {
// Simulate sending to an error tracking service
console.log("[ErrorTracker] Reported:", error._tag, error.message);
}),
),
);
const request = HttpClientRequest.get(
`https://dummyjson.com/users/${userId}`,
);
const response = yield* client.execute(request);
const user = yield* response.json;
return user;
}).pipe(Effect.provide(FetchHttpClient.layer));
}
// Test with an invalid user — error is tracked, then still propagates
Effect.runPromise(fetchUserWithErrorTracking(9999)).then(
(user) => console.log("User:", user.firstName),
(error) => console.log("[Main] Unhandled error:", error.message),
);

Output:

Terminal
[ErrorTracker] Reported: HttpClientError StatusCode: non 2xx status code (404 GET https://dummyjson.com/users/9999)
[Main] Unhandled error: StatusCode: non 2xx status code (404 GET https://dummyjson.com/users/9999)

Notice both lines print. The tapError callback runs first, then the error continues to propagate. It’s not swallowed.

tapError is particularly useful for observability. You can add it to a client once, and every request through that client will have its errors observed without affecting the error handling strategy of the calling code.

Sign in to save progress

Stay in the loop

Get notified when new chapters are added and when this course is complete.