ミニプログラム開発において、API通信処理の一元化は保守性とセキュリティの両面で不可欠です。`wx.request`をプロミスベースのラッパーで包み、リクエスト・レスポンスの中間処理(インターセプター)を分離することで、認証トークンの自動付与や共通エラーハンドリングを画面ロジックから完全に切り離すことができます。
通信ディスパッチャーの構築
各HTTPメソッドごとに独立した関数を用意する従来の方式はコードが冗長になりやすいため、メソッド名を動的に設定する単一ゲートウェイ関数と、設定オブジェクトの組み合わせを採用します。
// httpClient.js
const coreDispatcher = (config) => {
return new Promise((onSuccess, onFail) => {
wx.request({
url: config.targetUrl,
method: config.httpMethod || 'GET',
data: config.payload || {},
header: config.customHeaders || {},
success: onSuccess,
fail: onFail
});
});
};
// 各メソッド用ラッパー(設定の事前マージ)
const apiGateway = {
get: (opts) => coreDispatcher({ ...opts, httpMethod: 'GET' }),
post: (opts) => coreDispatcher({ ...opts, httpMethod: 'POST' }),
put: (opts) => coreDispatcher({ ...opts, httpMethod: 'PUT' }),
delete: (opts) => coreDispatcher({ ...opts, httpMethod: 'DELETE' })
};
export { coreDispatcher, apiGateway };
インターセプターチェーンの実装
リクエスト送信前のヘッダー調整と、サーバー応答後のステータス判定・データ変換をチェーン形式で定義します。この構造により、後からロギングやレートリミッターなどのミドルウェアを挿入しやすくなります。
const createInterceptorPipeline = (chainHandlers) => {
const preprocessRequest = (requestParams) => {
requestParams.customHeaders = { ...requestParams.customHeaders };
const appInstance = getApp();
requestParams.customHeaders['Authorization'] = `Bearer ${appInstance.globalData.sessionToken}`;
return chainHandlers.request ? chainHandlers.request(requestParams) : requestParams;
};
const postprocessResponse = (rawResponse) => {
const { statusCode, data } = rawResponse;
if (statusCode >= 200 && statusCode < 300) {
if (data.userContext) {
getApp().globalData.userContext = data.userContext;
}
return Promise.resolve(data);
}
const networkError = new Error(`通信異常: HTTP ${statusCode}`);
networkError.originalPayload = rawResponse;
return Promise.reject(networkError);
};
return {
beforeSend: preprocessRequest,
afterReceive: postprocessResponse
};
};
export { createInterceptorPipeline };
グローバル状態の保持
認証情報やユーザープロファイルは、アプリケーション起動時に初期化された`App`インスタンスの`globalData`で管理します。これにより、ページ遷移やコンポーネントの再レンダリングでも状態が保持されます。
// app.js
App({
globalData: {
sessionToken: null,
userContext: null
}
});
実行フローと利用例
インターセプターで加工された設定を用いて通信を実行し、結果をチェーン処理で受け渡します。
import { apiGateway, createInterceptorPipeline } from './httpClient';
const middleware = createInterceptorPipeline({
request: (cfg) => {
console.log('[Interceptor] 送信準備:', cfg.targetUrl);
return cfg;
},
response: (res) => {
console.log('[Interceptor] 受信完了:', res.statusCode);
return res;
}
});
const executeWithPipeline = (requestConfig) => {
const enrichedConfig = middleware.beforeSend(requestConfig);
const methodKey = (enrichedConfig.httpMethod || 'GET').toLowerCase();
return apiGateway[methodKey](enrichedConfig)
.then(middleware.afterReceive);
};
// 呼び出し
executeWithPipeline({
targetUrl: 'https://api.example.com/v1/accounts',
httpMethod: 'GET'
})
.then(payload => console.log('レスポンスデータ:', payload))
.catch(failure => console.error('処理中断:', failure.message));
実装上の留意点
- 処理順序は必ず「リクエストフェーズ → ネイティブ通信 → レスポンスフェーズ」となっており、この順序は実行環境により保証されます。
- グローバル変数へのアクセスは`getApp()`を通じて行いますが、同期的に値が確定していないタイミングで呼び出すと`undefined`が返るため、初期化ロジックとの整合性を確認してください。
- 引数オブジェクトはスプレッド構文で浅いコピーを行う設計にしており、元の設定がインターセプター内で参照透過性を保ちつつ拡張されます。
- ベースURLやタイムアウト値は、必要に応じて`coreDispatcher`の呼び出し前にデフォルト値をマージするラッパーを別に定義するのが推奨されます。