如何识别Observable的多种返回类型?API多返回类型处理技术问询
I'm calling an API to fetch a product list. When an error occurs, the API returns a response containing a status code and error message. Here's the relevant code:
product-service.ts:
getAllProducts(): Observable<IProduct[] | ApiResponse> { return this._http.get<IProduct[]>(this._GetAllUrl) .do(data => console.log("All: " + JSON.stringify(data))) .catch(err => this.HandleError(err)); }
api-response.ts:
export class ApiResponse { StatusCode: number; Message: string; }
product.ts:
export interface IProduct { // Product properties defined here }
I want to know how to identify the multiple return types (IProduct[] or ApiResponse) from this Observable.
Answer
Great question! When dealing with an Observable that can emit one of multiple types, TypeScript type guards are your go-to solution. Let's break down how to handle this cleanly:
1. Create a Custom Type Guard Function
First, build a type guard to check if an object is an ApiResponse at runtime. This tells TypeScript exactly what type it's dealing with in different code paths:
function isApiResponse(obj: unknown): obj is ApiResponse { const response = obj as ApiResponse; return typeof response === 'object' && response !== null && 'StatusCode' in response && typeof response.StatusCode === 'number' && 'Message' in response && typeof response.Message === 'string'; }
2. Use the Type Guard in Your Subscription
When subscribing to the Observable, use the type guard to branch logic based on the returned type:
this.productService.getAllProducts().subscribe({ next: (data) => { if (isApiResponse(data)) { // TypeScript now knows data is an ApiResponse console.error(`Request failed: ${data.StatusCode} - ${data.Message}`); // Handle error UI state (show alert, disable buttons, etc.) } else { // TypeScript now knows data is an IProduct[] console.log('Fetched products:', data); // Render your product list } }, error: (err) => { // Catch unexpected errors that your HandleError might not cover console.error('Unexpected error:', err); } });
3. Optional: Improve the Return Type with a Discriminated Union (Best Practice)
For even clearer type handling, wrap your success/error states in a discriminated union. This makes type checks more explicit and harder to mess up:
// Define a union type with a discriminator property type ProductFetchResult = | { type: 'success', products: IProduct[] } | { type: 'error', error: ApiResponse }; // Update your service method to return this union type getAllProducts(): Observable<ProductFetchResult> { return this._http.get<IProduct[]>(this._GetAllUrl) .pipe( tap(data => console.log("All: " + JSON.stringify(data))), map(data => ({ type: 'success', products: data })), catchError(err => { const apiError = this.HandleError(err); return of({ type: 'error', error: apiError }); }) ); }
Now subscribing becomes even more intuitive with a switch statement:
this.productService.getAllProducts().subscribe({ next: (result) => { switch(result.type) { case 'success': console.log('Products loaded:', result.products); // Render product list break; case 'error': console.error('Load failed:', result.error.StatusCode, result.error.Message); // Show error message to user break; } } });
Quick Note on Your HandleError Method
Ensure your HandleError function correctly returns an Observable<ApiResponse> so the catch block maps errors to the expected type. Here's a quick example of how that might look:
private HandleError(error: HttpErrorResponse): Observable<ApiResponse> { let statusCode = error.status; let message = 'Unknown error occurred'; if (error.error instanceof ErrorEvent) { // Client-side error message = `Client error: ${error.error.message}`; } else if (error.error && isApiResponse(error.error)) { // Server returned a valid ApiResponse return of(error.error); } else { // Fallback for non-standard server errors message = `Server error: ${error.message}`; } return of({ StatusCode: statusCode, Message: message }); }
内容的提问来源于stack exchange,提问作者Reed

