Skip to content

Commit a7eab29

Browse files
committed
feat(index.ts): added late response handling with onLateResponse callback
- Introduced `allowLateResponseAfterTimeout` flag in `sendRequest` method to enable late response handling. - Added `onLateResponse` callback in `sendRequest` method to handle late responses after request timeout. - Modified `sendRequest` to store timed-out requests conditionally in `timedOutRequests` object. - Updated response handler to invoke `onLateResponse` for late responses. - Improved error handling by passing `CustomError` or response to the callback. - Added detailed logging for late response handling. - Updated README with documentation for late response handling and new error codes. - Updated `auth-service` example to demonstrate late response handling in the README. This feature allows users to handle late responses gracefully, providing better flexibility for real-time communication in microservices.
1 parent 505fda3 commit a7eab29

File tree

2 files changed

+154
-11
lines changed

2 files changed

+154
-11
lines changed

README.md

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ License: [MIT](./LICENSE)
6666
- 🔗 Service discovery and registration (provided by the hub).
6767
- 📡 Request routing and response handling (provided by the hub).
6868
- ❤️ Heartbeat mechanism to detect and remove inactive services (provided by the hub).
69+
- ⏳ Late response handling even after a request timeout.
6970

7071
<hr>
7172

@@ -108,9 +109,14 @@ In this setup:
108109
- Once connected, the **[Hub](https://github.com/arijitcodes/microstream-hub)** keeps track of all connected services, so you don’t need to manually configure connections between services. However, you still need to specify the target service and method when sending a request.
109110

110111
4. **Heartbeat Mechanism**:
112+
111113
- Services send regular "heartbeats" to the **[Hub](https://github.com/arijitcodes/microstream-hub)** to confirm they’re active.
112114
- If a service stops sending heartbeats, the **[Hub](https://github.com/arijitcodes/microstream-hub)** removes it from the network.
113115

116+
5. **Late Response Handling**:
117+
- If a request times out, you can optionally allow late responses to be handled via a callback.
118+
- This is useful for scenarios where the target service may respond after the timeout period - and the request is for something that can be done in the background or processed later.
119+
114120
### ✨ Why Choose MicroStream?
115121

116122
**MicroStream** is designed to make microservice communication **simple**, **efficient**, and **scalable**. Here’s why you’ll love it:
@@ -121,6 +127,7 @@ In this setup:
121127
- **Scalable**: Easily add more services without reconfiguring the network.
122128
- **Lightweight**: Minimal overhead compared to traditional REST or gRPC.
123129
- **Flexible**: Works seamlessly with any microservice architecture.
130+
- **Late Response Handling**: Gracefully handle responses that arrive after a timeout.
124131

125132
<hr>
126133

@@ -177,6 +184,26 @@ try {
177184
} catch (error) {
178185
console.log("Error:", error.message);
179186
}
187+
188+
// Example of late response handling
189+
try {
190+
const response = await client.sendRequest(
191+
"jwt-service",
192+
"generate_jwt",
193+
{ userId: 123 },
194+
true, // Allow late responses
195+
(error, res) => {
196+
if (error) {
197+
console.log("Late response error:", error.message);
198+
} else {
199+
console.log("Late response received:", res);
200+
}
201+
}
202+
);
203+
console.log("Received response:", response);
204+
} catch (error) {
205+
console.log("Error:", error.message);
206+
}
180207
```
181208

182209
### Explanation 👨🏻‍🏫
@@ -187,12 +214,20 @@ try {
187214
- `event`: The event name to listen for.
188215
- `handler`: The function to handle the request. It receives the request data and returns the response.
189216
3. **Sending Requests**: The `sendRequest` method is used to send a request to another service. In this example, requests are sent to the "jwt-service" to generate a JWT and to the "profile-service" to fetch a profile by user ID.
190-
- **Parameters**:
191-
- `targetService`: The name of the target service.
192-
- `event`: The event name to trigger on the target service.
193-
- `data`: Optional data to send with the request.
194-
- **Returns**: A promise that resolves with the response from the target service.
195-
- **Error Handling**: The `sendRequest` method is wrapped in a try-catch block to handle any errors that may occur during the request. For example, if a request is sent to an invalid service, the [Hub](#microstream-hub-) will respond with an error, which will be received by the client and thrown accordingly. The catch block will catch the error, and the user can display it using the `error.message` property.
217+
218+
- **Parameters**:
219+
220+
- `targetService`: The name of the target service.
221+
- `event`: The event name to trigger on the target service.
222+
- `data`: Optional data to send with the request.
223+
- `allowLateResponseAfterTimeout`: Whether to allow handling late responses after the request times out (default: `false`).
224+
- `onLateResponse`: Optional callback to handle late responses. This callback is invoked if:
225+
- `allowLateResponseAfterTimeout` is true, and
226+
- A late response is received after the request has timed out.
227+
228+
- **Returns**: A promise that resolves with the response from the target service.
229+
230+
- **Error Handling**: The `sendRequest` method is wrapped in a try-catch block to handle any errors that may occur during the request. For example, if a request is sent to an invalid service, the [**Hub**](#microstream-hub-) will respond with an error, which will be received by the client and thrown accordingly. The catch block will catch the error, and the user can display it using the `error.message` property. For more error related info, please have a look at the [Error Structure](#error-structure-) or the [Error Handling Section](#error-handling-)
196231

197232
<hr>
198233

@@ -279,11 +314,12 @@ try {
279314

280315
### Error Handling Best Practices 🎯
281316

282-
1. Always wrap requests in try-catch blocks
317+
1. Always wrap requests in `try-catch` blocks
283318
2. Check error codes for specific error handling
284-
3. Use error.errorData for additional context in debugging
285-
4. Handle REQUEST_TIMEOUT errors with appropriate retry logic
286-
5. Implement proper logging for INTERNAL_SERVER_ERROR cases
319+
3. Use `error.errorData` for additional context in debugging
320+
4. Handle `REQUEST_TIMEOUT` errors with appropriate retry logic
321+
5. Implement proper logging for `INTERNAL_SERVER_ERROR` cases
322+
6. Use `allowLateResponseAfterTimeout` and `onLateResponse` to handle late responses gracefully
287323

288324
### Common Error Scenarios 🔄
289325

@@ -306,10 +342,17 @@ try {
306342
- Useful for debugging service-side issues
307343

308344
4. **Service Registration Errors**
345+
309346
- Occurs during initial connection
310347
- Critical errors that may require process termination
311348
- Check for duplicate service names in your network
312349

350+
5. **Late Response Errors**
351+
352+
- Occurs when a response is received after the request has timed out
353+
- Includes the original request payload and response data
354+
- Use `onLateResponse` to handle these scenarios gracefully
355+
313356
<hr>
314357

315358
## MicroStream Hub 🏢

src/index.ts

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@ export class MicrostreamClient {
4444
private handlers: { [event: string]: (data: any) => any } = {};
4545
private pendingRequests: { [requestId: string]: (response: any) => void } =
4646
{};
47+
private timedOutRequests: {
48+
[requestId: string]: {
49+
allowLateResponseAfterTimeout: boolean;
50+
onLateResponse?: (
51+
error: CustomError | null,
52+
response: {
53+
response: any;
54+
originalPayload: RequestPayload;
55+
} | null
56+
) => void;
57+
originalPayload: RequestPayload; // In case we use it in future
58+
};
59+
} = {};
4760
private logger: Logger; // Add logger instance
4861

4962
/**
@@ -149,6 +162,8 @@ export class MicrostreamClient {
149162
`[${this.serviceName}] Received response for request ${id}`,
150163
response
151164
);
165+
166+
/*
152167
if (this.pendingRequests[id]) {
153168
this.pendingRequests[id](response);
154169
delete this.pendingRequests[id];
@@ -158,6 +173,60 @@ export class MicrostreamClient {
158173
response
159174
);
160175
}
176+
*/
177+
178+
// First check if the request is a Normal Pending Request
179+
if (this.pendingRequests[id]) {
180+
this.pendingRequests[id](response);
181+
delete this.pendingRequests[id];
182+
return;
183+
}
184+
185+
// Else - Check if the request is in timedOutRequests
186+
const timedOutRequest = this.timedOutRequests[id];
187+
188+
if (timedOutRequest) {
189+
this.logger.warn(
190+
`Received response for timed-out request ${id}`,
191+
response
192+
);
193+
194+
// Check if late responses are allowed
195+
if (timedOutRequest.allowLateResponseAfterTimeout) {
196+
// Invoke the onLateResponse callback if it exists
197+
this.logger.info(
198+
`Invoking onLateResponse callback for timed-out request ${id}`
199+
);
200+
201+
if (timedOutRequest.onLateResponse) {
202+
// If the response contains an error, pass it as the first argument
203+
if (response.error) {
204+
timedOutRequest.onLateResponse(
205+
response.error, // Error object
206+
null // No response
207+
);
208+
} else {
209+
// If the response is successful, pass null as the first argument and the response as the second argument
210+
timedOutRequest.onLateResponse(
211+
null, // No error
212+
{
213+
response,
214+
originalPayload: timedOutRequest.originalPayload,
215+
}
216+
);
217+
}
218+
}
219+
}
220+
221+
// Clean up the timed-out request
222+
delete this.timedOutRequests[id];
223+
} else {
224+
// Completely unexpected response
225+
this.logger.warn(
226+
`[${this.serviceName}] Received unexpected response for request ${id}`,
227+
response
228+
);
229+
}
161230
});
162231

163232
// Handle socket connection rejection error by the hub
@@ -205,15 +274,35 @@ export class MicrostreamClient {
205274

206275
/**
207276
* Sends a request to another service through the Microstream Hub.
277+
*
208278
* @param targetService - The name of the target service.
209279
* @param event - The event name to trigger on the target service.
210280
* @param data - Optional data to send with the request.
281+
* @param allowLateResponseAfterTimeout - Whether to allow handling late responses after the request times out.
282+
* If `true`, the `onLateResponse` callback will be invoked if a late response is received.
283+
* Defaults to `false`.
284+
* @param onLateResponse - Optional callback to handle late responses. This callback is invoked if:
285+
* 1. `allowLateResponseAfterTimeout` is `true`, and
286+
* 2. A late response is received after the request has timed out.
287+
* The callback receives two arguments:
288+
* - `error`: A `CustomError` object if the late response contains an error, or `null` if the response is successful.
289+
* - `response`: An object containing the `response` and `originalPayload` if the response is successful, or `null` if there’s an error.
211290
* @returns A promise that resolves with the response from the target service.
291+
* If the request times out, the promise is rejected with a `CustomError`. For specific error codes, refer to the documentation.
292+
* If a late response is received and `allowLateResponseAfterTimeout` is `true`, the `onLateResponse` callback is invoked.
212293
*/
213294
public sendRequest(
214295
targetService: string,
215296
event: string,
216-
data?: any
297+
data?: any,
298+
allowLateResponseAfterTimeout: boolean = false,
299+
onLateResponse?: (
300+
error: CustomError | null,
301+
response: {
302+
response: any;
303+
originalPayload: RequestPayload;
304+
} | null
305+
) => void
217306
): Promise<any> {
218307
return new Promise((resolve, reject) => {
219308
const requestId = nanoid();
@@ -224,6 +313,15 @@ export class MicrostreamClient {
224313

225314
// Set a timeout for the request
226315
const timeoutHandler = setTimeout(() => {
316+
// Only add the timed out request to timedOutRequests object/map if late responses are allowed
317+
if (allowLateResponseAfterTimeout) {
318+
this.timedOutRequests[requestId] = {
319+
allowLateResponseAfterTimeout,
320+
onLateResponse,
321+
originalPayload: payload,
322+
};
323+
}
324+
227325
// reject(new Error("Request timeout"));
228326
reject(
229327
/* new Error(
@@ -235,6 +333,8 @@ export class MicrostreamClient {
235333
{ targetService, event, data }
236334
)
237335
);
336+
337+
// Delete from PendingRequests
238338
delete this.pendingRequests[requestId]; // Clean up
239339
}, this.timeout);
240340

0 commit comments

Comments
 (0)