7.1 HTTP 客户端封装
7.1.1 基础 HTTP 客户端
在 Sciter-JS 中,我们可以使用内置的网络 API 或封装自己的 HTTP 客户端来处理网络请求。
graph TB
A[HTTP 客户端] --> B[请求拦截器]
A --> C[响应拦截器]
A --> D[缓存管理]
A --> E[错误处理]
B --> F[认证处理]
B --> G[请求日志]
B --> H[参数处理]
C --> I[响应转换]
C --> J[错误检查]
C --> K[数据验证]
D --> L[内存缓存]
D --> M[本地存储]
D --> N[缓存策略]
// 基础 HTTP 客户端
class HttpClient {
constructor(options = {}) {
this.baseURL = options.baseURL || '';
this.timeout = options.timeout || 10000;
this.headers = options.headers || {};
this.interceptors = {
request: [],
response: []
};
}
// 请求拦截器
addRequestInterceptor(interceptor) {
this.interceptors.request.push(interceptor);
}
// 响应拦截器
addResponseInterceptor(interceptor) {
this.interceptors.response.push(interceptor);
}
// 核心请求方法
async request(config) {
try {
// 合并配置
const requestConfig = {
method: 'GET',
headers: { ...this.headers },
timeout: this.timeout,
...config,
url: this.buildURL(config.url)
};
// 执行请求拦截器
const processedConfig = await this.executeRequestInterceptors(requestConfig);
// 发送请求
const response = await this.sendRequest(processedConfig);
// 执行响应拦截器
const processedResponse = await this.executeResponseInterceptors(response);
return processedResponse;
} catch (error) {
throw this.handleError(error);
}
}
// 便捷方法
get(url, config = {}) {
return this.request({ ...config, method: 'GET', url });
}
post(url, data, config = {}) {
return this.request({ ...config, method: 'POST', url, data });
}
put(url, data, config = {}) {
return this.request({ ...config, method: 'PUT', url, data });
}
delete(url, config = {}) {
return this.request({ ...config, method: 'DELETE', url });
}
}
7.2 请求拦截和响应处理
7.2.1 认证拦截器
// 认证拦截器
class AuthInterceptor {
constructor(tokenProvider) {
this.tokenProvider = tokenProvider;
}
// 请求拦截器
requestInterceptor = async (config) => {
const token = await this.tokenProvider.getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}
// 响应拦截器
responseInterceptor = async (response) => {
// 处理 401 未授权
if (response.status === 401) {
await this.tokenProvider.refreshToken();
throw new Error('TOKEN_EXPIRED');
}
return response;
}
}
// Token 提供者
class TokenProvider {
constructor() {
this.token = null;
this.refreshToken = null;
this.refreshPromise = null;
}
async getToken() {
if (!this.token || this.isTokenExpired()) {
await this.refreshTokens();
}
return this.token;
}
async refreshTokens() {
// 防止并发刷新
if (this.refreshPromise) {
return this.refreshPromise;
}
this.refreshPromise = this.performTokenRefresh();
try {
await this.refreshPromise;
} finally {
this.refreshPromise = null;
}
}
isTokenExpired() {
if (!this.token) return true;
try {
const payload = JSON.parse(atob(this.token.split('.')[1]));
return Date.now() >= payload.exp * 1000;
} catch {
return true;
}
}
}
7.3 数据缓存策略
7.3.1 缓存管理器
// 缓存策略枚举
const CacheStrategy = {
CACHE_FIRST: 'cache-first',
NETWORK_FIRST: 'network-first',
CACHE_ONLY: 'cache-only',
NETWORK_ONLY: 'network-only',
STALE_WHILE_REVALIDATE: 'stale-while-revalidate'
};
// 内存缓存存储
class MemoryCacheStorage {
constructor(maxSize = 100) {
this.cache = new Map();
this.maxSize = maxSize;
this.accessOrder = [];
}
async get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
this.cache.delete(key);
this.removeFromAccessOrder(key);
return null;
}
// 更新访问顺序
this.updateAccessOrder(key);
return item.value;
}
async set(key, value, ttl = 300000) {
// 检查容量限制
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
this.evictLeastRecentlyUsed();
}
this.cache.set(key, {
value,
expiry: Date.now() + ttl
});
this.updateAccessOrder(key);
}
// LRU 淘汰策略
evictLeastRecentlyUsed() {
if (this.accessOrder.length > 0) {
const lruKey = this.accessOrder[0];
this.cache.delete(lruKey);
this.accessOrder.shift();
}
}
updateAccessOrder(key) {
this.removeFromAccessOrder(key);
this.accessOrder.push(key);
}
removeFromAccessOrder(key) {
const index = this.accessOrder.indexOf(key);
if (index > -1) {
this.accessOrder.splice(index, 1);
}
}
}
7.4 数据处理和转换
7.4.1 数据转换器
// 数据转换器基类
class DataTransformer {
transform(data) {
throw new Error('子类必须实现 transform 方法');
}
reverse(data) {
throw new Error('子类必须实现 reverse 方法');
}
}
// 日期转换器
class DateTransformer extends DataTransformer {
constructor(options = {}) {
super();
this.dateFields = options.dateFields || [];
this.autoDetect = options.autoDetect !== false;
}
transform(data) {
if (!data || typeof data !== 'object') return data;
const result = Array.isArray(data) ? [] : {};
for (const [key, value] of Object.entries(data)) {
if (this.shouldTransformField(key, value)) {
result[key] = this.parseDate(value);
} else if (typeof value === 'object') {
result[key] = this.transform(value);
} else {
result[key] = value;
}
}
return result;
}
shouldTransformField(fieldName, value) {
if (this.dateFields.length > 0) {
return this.dateFields.includes(fieldName);
}
if (!this.autoDetect) return false;
return typeof value === 'string' &&
(this.isDateField(fieldName) || this.isDateString(value));
}
isDateField(fieldName) {
const datePatterns = [
/date/i, /time/i, /created/i, /updated/i
];
return datePatterns.some(pattern => pattern.test(fieldName));
}
isDateString(str) {
if (typeof str !== 'string') return false;
return /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(str);
}
parseDate(dateString) {
try {
const date = new Date(dateString);
return isNaN(date.getTime()) ? dateString : date;
} catch {
return dateString;
}
}
}
7.4.2 数据验证系统
// 数据验证器
class DataValidator {
constructor() {
this.rules = new Map();
this.setupBuiltinRules();
}
// 验证数据
validate(data, schema) {
const errors = [];
this.validateObject(data, schema, [], errors);
return {
isValid: errors.length === 0,
errors
};
}
validateObject(data, schema, path, errors) {
if (!schema || typeof schema !== 'object') return;
for (const [field, rules] of Object.entries(schema)) {
const fieldPath = [...path, field];
const value = data?.[field];
this.validateField(value, rules, fieldPath, errors);
}
}
validateField(value, rules, path, errors) {
if (Array.isArray(rules)) {
rules.forEach(rule => this.validateField(value, rule, path, errors));
return;
}
if (typeof rules === 'string') {
this.applyRule(value, rules, path, errors);
}
}
applyRule(value, ruleName, path, errors) {
const rule = this.rules.get(ruleName);
if (!rule) return;
const result = rule(value);
if (!result.isValid) {
errors.push({
path: path.join('.'),
rule: ruleName,
message: result.message,
value
});
}
}
setupBuiltinRules() {
// 必填验证
this.rules.set('required', (value) => {
const isValid = value !== null && value !== undefined && value !== '';
return {
isValid,
message: isValid ? '' : '此字段为必填项'
};
});
// 邮箱验证
this.rules.set('email', (value) => {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const isValid = emailPattern.test(value);
return {
isValid,
message: isValid ? '' : '邮箱格式不正确'
};
});
}
}
7.5 实践练习
7.5.1 完整的数据服务实现
// 用户数据服务
class UserService {
constructor() {
this.http = new HttpClient({
baseURL: '/api',
timeout: 10000
});
this.setupInterceptors();
this.setupTransformers();
this.setupValidators();
}
setupInterceptors() {
const tokenProvider = new TokenProvider();
const authInterceptor = new AuthInterceptor(tokenProvider);
this.http.addRequestInterceptor(authInterceptor.requestInterceptor);
this.http.addResponseInterceptor(authInterceptor.responseInterceptor);
}
setupTransformers() {
this.transformer = new DateTransformer({
dateFields: ['createdAt', 'updatedAt']
});
}
setupValidators() {
this.validator = new DataValidator();
this.userSchema = {
name: ['required'],
email: ['required', 'email'],
age: ['required']
};
}
// 获取用户列表
async getUsers(params = {}) {
try {
const response = await this.http.get('/users', { params });
return this.transformer.transform(response.data);
} catch (error) {
throw new Error(`获取用户列表失败: ${error.message}`);
}
}
// 创建用户
async createUser(userData) {
// 验证数据
const validation = this.validator.validate(userData, this.userSchema);
if (!validation.isValid) {
throw new Error('数据验证失败');
}
try {
const response = await this.http.post('/users', userData);
return this.transformer.transform(response.data);
} catch (error) {
throw new Error(`创建用户失败: ${error.message}`);
}
}
// 更新用户
async updateUser(id, userData) {
try {
const response = await this.http.put(`/users/${id}`, userData);
return this.transformer.transform(response.data);
} catch (error) {
throw new Error(`更新用户失败: ${error.message}`);
}
}
// 删除用户
async deleteUser(id) {
try {
await this.http.delete(`/users/${id}`);
return true;
} catch (error) {
throw new Error(`删除用户失败: ${error.message}`);
}
}
}
7.5.2 使用示例
<!DOCTYPE html>
<html>
<head>
<title>网络请求示例</title>
<style>
.user-list {
margin: 20px;
}
.user-item {
padding: 10px;
border: 1px solid #ddd;
margin: 5px 0;
border-radius: 4px;
}
.form-group {
margin: 10px 0;
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-group input {
width: 200px;
padding: 5px;
}
.btn {
padding: 8px 16px;
margin: 5px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.error {
color: red;
margin: 10px 0;
}
</style>
</head>
<body>
<div id="app">
<h1>用户管理</h1>
<div class="form-section">
<h2>添加用户</h2>
<div class="form-group">
<label>姓名:</label>
<input type="text" id="name" placeholder="请输入姓名">
</div>
<div class="form-group">
<label>邮箱:</label>
<input type="email" id="email" placeholder="请输入邮箱">
</div>
<div class="form-group">
<label>年龄:</label>
<input type="number" id="age" placeholder="请输入年龄">
</div>
<button class="btn btn-primary" onclick="createUser()">添加用户</button>
<div id="error" class="error"></div>
</div>
<div class="user-list">
<h2>用户列表</h2>
<button class="btn" onclick="loadUsers()">刷新列表</button>
<div id="users"></div>
</div>
</div>
<script>
// 初始化用户服务
const userService = new UserService();
// 加载用户列表
async function loadUsers() {
try {
const users = await userService.getUsers();
displayUsers(users);
} catch (error) {
showError(error.message);
}
}
// 显示用户列表
function displayUsers(users) {
const container = document.getElementById('users');
container.innerHTML = '';
users.forEach(user => {
const userDiv = document.createElement('div');
userDiv.className = 'user-item';
userDiv.innerHTML = `
<strong>${user.name}</strong> (${user.email})
<br>年龄: ${user.age}
<br>创建时间: ${user.createdAt ? user.createdAt.toLocaleString() : 'N/A'}
<button class="btn" onclick="deleteUser(${user.id})">删除</button>
`;
container.appendChild(userDiv);
});
}
// 创建用户
async function createUser() {
const userData = {
name: document.getElementById('name').value,
email: document.getElementById('email').value,
age: parseInt(document.getElementById('age').value)
};
try {
await userService.createUser(userData);
clearForm();
loadUsers();
showError(''); // 清除错误信息
} catch (error) {
showError(error.message);
}
}
// 删除用户
async function deleteUser(id) {
if (confirm('确定要删除这个用户吗?')) {
try {
await userService.deleteUser(id);
loadUsers();
} catch (error) {
showError(error.message);
}
}
}
// 清除表单
function clearForm() {
document.getElementById('name').value = '';
document.getElementById('email').value = '';
document.getElementById('age').value = '';
}
// 显示错误信息
function showError(message) {
document.getElementById('error').textContent = message;
}
// 页面加载时获取用户列表
document.addEventListener('DOMContentLoaded', loadUsers);
</script>
</body>
</html>
7.6 本章小结
7.6.1 核心概念
- HTTP 客户端封装: 提供统一的网络请求接口
- 拦截器模式: 在请求和响应过程中插入自定义逻辑
- 缓存策略: 提高应用性能和用户体验
- 数据转换: 统一前后端数据格式
- 数据验证: 确保数据的完整性和正确性
7.6.2 技术要点
- 请求拦截器用于添加认证信息、日志记录等
- 响应拦截器用于错误处理、数据转换等
- 多级缓存提供更好的性能和可靠性
- 数据转换器支持链式组合和双向转换
- 验证系统支持复杂的验证规则和自定义验证
7.6.3 最佳实践
- 合理设置缓存策略和过期时间
- 使用拦截器统一处理通用逻辑
- 实现完善的错误处理机制
- 对敏感数据进行验证和转换
- 提供友好的用户反馈
7.6.4 下一章预告
下一章我们将学习性能优化与调试技巧,包括: - 性能监控和分析 - 内存管理和优化 - 渲染性能优化 - 调试工具和技巧 - 性能测试方法