axios-impostor
v1.6.1
Published
**繁體中文** | [English](./README.md)
Maintainers
Readme
繁體中文 | English
Axios Impostor 🎭
輕量級的 HTTP 客戶端,API 設計類似 Axios。這份說明專注在:怎麼用、有哪些型別、建議的專案結構。
安裝
npm install axios-impostor
# 或
pnpm add axios-impostor
# 或
yarn add axios-impostor1. 建議的使用方式
在你的專案裡,建議這樣拆:
- 建立一個共用的 client 實例
- 放在像是
src/api/client.ts的檔案裡。 - 在這裡設定
baseURL、timeout、預設headers、interceptors 等。
- 放在像是
- 每個領域/模組各自一組 API 函式
- 例如
src/api/users.ts裡面只放「使用者相關」的 API。 - 這些函式內部呼叫 client 方法,回傳明確的型別。
- 例如
- 錯誤處理集中在 UI 或服務層
- 當需要依 HTTP 狀態碼、錯誤 code 做判斷時,使用
FetchClientError。
- 當需要依 HTTP 狀態碼、錯誤 code 做判斷時,使用
- SSE 相關放在獨立模組
- 例如
src/api/stream.ts,用於聊天室、通知、AI 串流等。
- 例如
2. 快速開始範例
import { createFetchClient, FetchClientError } from 'axios-impostor';
// 1. 定義後端回傳的資料型別
interface User {
id: number;
name: string;
email: string;
}
// 2. 建立共用 client(建議放在 src/api/client.ts)
export const api = createFetchClient({
baseURL: 'https://jsonplaceholder.typicode.com',
timeout: 10000,
});
// 3. 建立 endpoint 輔助函式(例如放在 src/api/users.ts)
export async function getUser(userId: number): Promise<User> {
const response = await api.get<User>(`/users/${userId}`);
return response.data; // 取出實際的使用者資料
}
export async function createUser(input: Pick<User, 'name' | 'email'>): Promise<User> {
const response = await api.post<User, typeof input>('/users', input);
return response.data;
}
// 4. 在 UI / service 內使用
async function example() {
try {
const user = await getUser(1);
console.log('User name:', user.name);
} catch (error) {
if (error instanceof FetchClientError) {
console.error('請求失敗', {
code: error.code,
status: error.response?.status,
url: error.config.url,
});
}
throw error;
}
}3. 理解 FetchResponse
所有 HTTP 方法(get、post、put、patch、delete)都回傳 FetchResponse<T> 物件:
interface FetchResponse<T> {
data: T; // ← 你的後端回應資料
status: number; // ← HTTP 狀態碼(200、404 等)
statusText: string; // ← 狀態文字("OK"、"Not Found" 等)
headers: Headers; // ← 回應標頭
config: CustomRequestInit; // ← 使用的請求設定
}為什麼要包裝回應?
這讓你可以同時存取資料和中繼資料:
const response = await api.get<User>('/users/1');
// 存取資料
console.log(response.data.name);
// 檢查狀態
if (response.status === 200) {
console.log('成功!');
}
// 讀取標頭
const contentType = response.headers.get('Content-Type');
// 查看請求了什麼
console.log('請求:', response.config.url);如果你偏好不包裝的資料:
只要在輔助函式中取出 .data 即可(如快速開始範例所示)。
4. SSE 串流範例
用於 Server-Sent Events(聊天、AI 回應、即時更新):
import { api } from './client';
import type { SSEMessage, SSEConnection } from 'axios-impostor';
export function subscribeChat(roomId: string, onMessage: (data: unknown) => void): SSEConnection {
const connection = api.sse(`/chat/rooms/${roomId}/stream`, {
headers: {
Authorization: 'Bearer your-token',
},
onOpen: () => {
console.log('SSE 已連線');
},
onMessage: (message: SSEMessage) => {
// 大多數後端會在 message.data 中傳送 JSON
try {
const parsed = JSON.parse(message.data);
onMessage(parsed);
} catch {
onMessage(message.data);
}
},
onError: (error) => {
console.error('SSE 錯誤', error);
},
onClose: () => {
console.log('SSE 已關閉');
},
});
return connection;
}
// 使用方式
const connection = subscribeChat('room-1', (payload) => {
console.log('聊天更新:', payload);
});
// 停止監聽
connection.close();5. API 參考
createFetchClient(options?)
建立可重複使用的 HTTP 客戶端。
選項(CreateFetchClientProp):
| 選項 | 型別 | 說明 |
| ------------- | -------------------- | ------------------------------------------------------- |
| baseURL | string | 所有請求前綴的基礎 URL |
| headers | HeadersInit | 所有請求的預設標頭 |
| timeout | number | 預設逾時時間(毫秒) |
| credentials | RequestCredentials | Cookie 策略:'omit' | 'same-origin' | 'include' |
回傳(FetchClient):
interface FetchClient {
// HTTP 方法 - 所有都回傳 FetchResponse<T>
get<T>(endpoint: string, options?: CustomRequestInit): Promise<FetchResponse<T>>;
post<T, B>(endpoint: string, body?: B, options?: CustomRequestInit): Promise<FetchResponse<T>>;
put<T, B>(endpoint: string, body?: B, options?: CustomRequestInit): Promise<FetchResponse<T>>;
patch<T, B>(endpoint: string, body?: B, options?: CustomRequestInit): Promise<FetchResponse<T>>;
delete<T>(endpoint: string, options?: CustomRequestInit): Promise<FetchResponse<T>>;
// 串流
sse(endpoint: string, options: SSEOptions): SSEConnection;
// 攔截器
interceptors: {
request: InterceptorManager<CustomRequestInit>;
response: InterceptorManager<Response>;
};
}特殊行為:
- 所有方法都會自動解析 JSON
204 No Content回傳{ data: null, status: 204, ... }- 非 2xx 狀態碼會拋出
FetchClientError
攔截器
新增全域的請求/回應處理:
// 為所有請求加上 auth token
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
}
return config;
});
// 全域處理 401
api.interceptors.response.use(
(response) => response,
(error) => {
if (error instanceof FetchClientError && error.response?.status === 401) {
// 重導向到登入頁
window.location.href = '/login';
}
throw error;
},
);6. 型別參考
FetchResponse<T>
所有 HTTP 方法回傳的包裝回應物件:
interface FetchResponse<T> {
data: T; // 你的型別化回應資料
status: number; // HTTP 狀態碼
statusText: string; // 狀態訊息
headers: Headers; // 回應標頭
config: CustomRequestInit; // 使用的請求設定
}CustomRequestInit
擴展原生 RequestInit:
interface CustomRequestInit extends RequestInit {
timeout?: number; // 單次請求的逾時覆寫
isStream?: boolean; // 內部旗標(自動管理)
url?: string; // 內部設定
baseURL?: string; // 內部設定
}FetchClientError
請求失敗時拋出:
class FetchClientError extends Error {
code?: string; // 'ERR_NETWORK', 'ERR_BAD_RESPONSE', 'ECONNABORTED'
config: CustomRequestInit; // 失敗的請求
request?: Request; // 原生 Request 物件
response?: Response; // 原生 Response 物件(如果有的話)
}用於錯誤處理:
try {
await api.get('/data');
} catch (error) {
if (error instanceof FetchClientError) {
if (error.code === 'ECONNABORTED') {
console.log('請求逾時');
}
if (error.response?.status === 404) {
console.log('找不到資源');
}
}
}SSEMessage
來自 Server-Sent Events 串流的一個事件:
interface SSEMessage {
data: string; // 訊息內容(通常是 JSON 字串)
event?: string; // 事件類型
id?: string; // 訊息 ID(用於重連)
retry?: number; // 重試延遲(毫秒)
}SSEOptions
sse() 的設定:
interface SSEOptions extends Omit<CustomRequestInit, 'method'> {
onOpen?: () => void;
onMessage: (message: SSEMessage) => void;
onError?: (error: Error) => void;
onClose?: () => void;
}SSEConnection
sse() 回傳的控制物件:
interface SSEConnection {
close(): void;
readonly readyState: 'connecting' | 'open' | 'closed';
}