Implementing a reusable HTTP client with request and response interception improves maintainability and centralizes common logic such as authentication and error handling. The following approach uses wx.request wrapped in promises and provides a modular structure for different HTTP methods.
Core Request Factory
A factory function standardizes the creation of requests for various HTTP verbs. It accepts configuration and returns a promise, reducing repetitive code.
function createHttpAction(method, config) {
return new Promise((resolve, reject) => {
wx.request({
url: config.url,
method: method,
data: config.params || {},
header: config.headers || {},
success: (res) => resolve(res),
fail: (err) => reject(err)
});
});
}
const httpClient = {
get: (cfg) => createHttpAction('GET', cfg),
post: (cfg) => createHttpAction('POST', cfg),
put: (cfg) => createHttpAction('PUT', cfg),
delete: (cfg) => createHttpAction('DELETE', cfg)
};
Response Status Evaluator
A dedicated function inspects the HTTP status and extracts the payload or throws a structured error. This separates response validation from business logic.
function evaluateStatus(httpResponse) {
const { statusCode, data } = httpResponse;
const isSuccess = statusCode >= 200 && statusCode < 300;
if (isSuccess) {
return data;
}
const failure = new Error(`Request failed with status ${statusCode}`);
failure.httpResponse = httpResponse;
throw failure;
}
Interceptor Chain Manager
Instead of directly exposing the raw transport, an interceptor manager wraps it. It accepts a chain containing the core request function and applies middleware for outgoing requests and incoming responses. This is where authorizatoin tokens are injected and global state is refreshed.
function applyInterceptors(chain) {
const beforeRequest = (options) => {
const appState = getApp().globalData;
const secureHeaders = { ...options.headers };
if (appState.token) {
secureHeaders['Authorization'] = `Bearer ${appState.token}`;
}
return chain.request({ ...options, headers: secureHeaders });
};
const afterResponse = (response) => {
const appState = getApp().globalData;
const { statusCode, data } = response;
if (statusCode >= 200 && statusCode < 300) {
if (data?.userInfo) {
appState.userInfo = data.userInfo;
}
return response;
}
const error = new Error(`Server responded with status ${statusCode}`);
error.rawResponse = response;
throw error;
};
return {
request: beforeRequest,
response: afterResponse
};
}
Global Application State
User session data and tokens are stored on the global app instance. This allows interceptors and other modules to access authantication context without repeated storage calls.
App({
globalData: {
userInfo: null,
token: null
}
});
Assembling the Exported Service
Combining the components yields a clean, promise-based API. Consumers call methods on the service, and request/response processing happens transparently through the interceptors.
const baseRequest = (config) => createHttpAction(config.method || 'GET', config);
const chain = { request: baseRequest };
const { request: interceptedRequest, response: interceptedResponse } = applyInterceptors(chain);
async function executeRequest(config) {
const finalOptions = await interceptedRequest(config);
const rawRes = await chain.request(finalOptions);
return interceptedResponse(rawRes);
}
const apiService = {
fetch: (config) => executeRequest(config).then(evaluateStatus),
post: (config) => executeRequest({ ...config, method: 'POST' }).then(evaluateStatus),
put: (config) => executeRequest({ ...config, method: 'PUT' }).then(evaluateStatus),
delete: (config) => executeRequest({ ...config, method: 'DELETE' }).then(evaluateStatus)
};
export default apiService;
This structure decouples concerns: HTTP transport, status checking, header injection, and global state updates each reside in their own layer.