<think>
我们之前已经封装了一个`TimedRequestScheduler`类,现在需要将其导出以便在多个模块中使用。
通常,在JavaScript/Node.js中,我们可以使用CommonJS模块(Node.js环境)或ES6模块(浏览器或Node.js环境)来导出和导入类。
下面我将分别展示:
1. 在Node.js环境中使用CommonJS模块导出
2. 在支持ES6模块的环境(如现代浏览器或Node.js)中导出
3. 在浏览器中直接通过script标签引入(使用IIFE封装)
我们假设文件名是`TimedRequestScheduler.js`。
### 方案1: CommonJS模块(Node.js)
```javascript
// TimedRequestScheduler.js
class TimedRequestScheduler {
// ... 之前的类实现代码
}
module.exports = TimedRequestScheduler;
```
使用方式(在另一个Node.js文件中):
```javascript
const TimedRequestScheduler = require('./TimedRequestScheduler');
const scheduler = new TimedRequestScheduler();
```
### 方案2: ES6模块
```javascript
// TimedRequestScheduler.js
export default class TimedRequestScheduler {
// ... 之前的类实现代码
}
```
使用方式(在另一个ES6模块中):
```javascript
import TimedRequestScheduler from './TimedRequestScheduler.js';
const scheduler = new TimedRequestScheduler();
```
### 方案3: 浏览器环境(通过script标签引入)
我们可以将类封装在一个立即执行函数表达式(IIFE)中,并将其附加到全局对象(如window)上。
```javascript
// TimedRequestScheduler.js
(function (global) {
class TimedRequestScheduler {
// ... 之前的类实现代码
}
// 暴露到全局
global.TimedRequestScheduler = TimedRequestScheduler;
})(window || this || {}); // 兼容不同环境
```
在HTML中使用:
```html
<script src="TimedRequestScheduler.js"></script>
<script>
const scheduler = new TimedRequestScheduler();
// 添加任务等...
</script>
```
### 完整代码(ES6模块导出示例)
以下是完整的`TimedRequestScheduler`类,以ES6模块方式导出:
```javascript
// TimedRequestScheduler.js
export default class TimedRequestScheduler {
constructor() {
this.tasks = new Map();
this.taskQueue = [];
this.maxConcurrent = 3;
this.activeRequests = 0;
this.defaultRetry = 2;
this.defaultTimeout = 8000;
}
addServiceTask(id, serviceCall, interval, options = {}) {
if (this.tasks.has(id)) {
console.warn(`任务ID ${id} 已存在,将被替换`);
this.removeServiceTask(id);
}
const task = {
id,
serviceCall,
interval,
retryCount: options.retryCount || this.defaultRetry,
timeout: options.timeout || this.defaultTimeout,
onSuccess: options.onSuccess || (() => {}),
onError: options.onError || (() => {}),
timerId: null,
lastRun: null,
nextRun: null,
errorCount: 0
};
this.tasks.set(id, task);
this.scheduleTask(id);
return id;
}
scheduleTask(id) {
const task = this.tasks.get(id);
if (!task) return;
if (task.timerId) clearTimeout(task.timerId);
const now = Date.now();
const nextRun = task.lastRun ? task.lastRun + task.interval : now;
task.timerId = setTimeout(() => {
this.executeTask(id);
}, Math.max(0, nextRun - now));
task.nextRun = nextRun;
}
async executeTask(id) {
const task = this.tasks.get(id);
if (!task) return;
task.lastRun = Date.now();
this.scheduleTask(id);
if (this.activeRequests >= this.maxConcurrent) {
this.taskQueue.push(id);
return;
}
this.activeRequests++;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), task.timeout);
const result = await task.serviceCall({ signal: controller.signal });
clearTimeout(timeoutId);
task.onSuccess(result);
task.errorCount = 0;
} catch (error) {
// 注意:这里需要清除timeoutId,但上面catch块中无法直接访问,因此需要在try块外定义timeoutId
// 为了解决这个问题,我们可以将timeoutId定义在try块之前
// 修改:将timeoutId的声明移到try外部
} finally {
// 但上面的catch块中并没有timeoutId,所以我们需要调整结构
// 下面我们重构一下try-catch结构
}
// 重构后的执行部分:
let timeoutId;
try {
const controller = new AbortController();
timeoutId = setTimeout(() => controller.abort(), task.timeout);
const result = await task.serviceCall({ signal: controller.signal });
clearTimeout(timeoutId);
task.onSuccess(result);
task.errorCount = 0;
} catch (error) {
if (timeoutId) clearTimeout(timeoutId);
task.errorCount++;
if (task.errorCount <= task.retryCount) {
console.warn(`任务 ${id} 失败,将重试 (${task.errorCount}/${task.retryCount})`);
setTimeout(() => this.executeTask(id), 1000);
} else {
task.onError(error);
task.errorCount = 0;
}
} finally {
this.activeRequests--;
if (this.taskQueue.length > 0 && this.activeRequests < this.maxConcurrent) {
const nextId = this.taskQueue.shift();
this.executeTask(nextId);
}
}
}
removeServiceTask(id) {
const task = this.tasks.get(id);
if (task && task.timerId) {
clearTimeout(task.timerId);
}
this.tasks.delete(id);
const index = this.taskQueue.indexOf(id);
if (index !== -1) this.taskQueue.splice(index, 1);
}
getTaskStatus() {
return Array.from(this.tasks.values()).map(task => ({
id: task.id,
nextRun: task.nextRun,
lastRun: task.lastRun,
errorCount: task.errorCount,
interval: task.interval
}));
}
pauseAll() {
for (const task of this.tasks.values()) {
if (task.timerId) {
clearTimeout(task.timerId);
task.timerId = null;
}
}
}
resumeAll() {
for (const task of this.tasks.values()) {
if (!task.timerId) {
this.scheduleTask(task.id);
}
}
}
}
```
### 注意事项
1. **环境兼容性**:
- 在浏览器中使用时注意`AbortController`兼容性(现代浏览器支持)
- Node.js环境需要15.0以上版本才支持`AbortController`(或使用`node-fetch`等库)
2. **错误处理**:
- 重构了`executeTask`方法中的错误处理,确保能清除超时定时器
3. **内存管理**:
- 移除任务时清理定时器,避免内存泄漏
### 使用示例(浏览器ES6模块)
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>定时任务调度器</title>
<script type="module">
import TimedRequestScheduler from './TimedRequestScheduler.js';
const scheduler = new TimedRequestScheduler();
// 添加任务示例
scheduler.addServiceTask('example-task', async () => {
console.log('任务执行中...');
return { data: 'success' };
}, 2000, {
onSuccess: (res) => console.log('成功:', res.data),
onError: (err) => console.error('失败:', err)
});
</script>
</head>
<body>
</body>
</html>
```
### 相关说明
- 在Node.js中使用时,如果版本低于15.0,可以使用`const { AbortController } = require('abort-controller');`并安装`abort-controller`包
- 在旧版浏览器中,如果需要支持`AbortController`,可以使用polyfill:`npm install abortcontroller-polyfill`
§§相关问题§§:
1. 如何在Node.js低版本中实现请求超时控制
2. 如何扩展定时任务调度器支持循环策略(如每天固定时间执行)
3. 如何实现定时任务的持久化存储(如保存到localStorage)
4. 如何在定时任务调度器中加入任务依赖关系
</think># 定时任务调度器的封装与使用
我将为您展示如何封装之前实现的定时任务调度器类,并通过多种方式导出和使用它。
## 完整的 TimedRequestScheduler 类封装
```javascript
// timed-request-scheduler.js
class TimedRequestScheduler {
constructor(options = {}) {
this.tasks = new Map();
this.taskQueue = [];
this.activeRequests = 0;
// 配置项
this.maxConcurrent = options.maxConcurrent || 3;
this.defaultRetry = options.defaultRetry || 2;
this.defaultTimeout = options.defaultTimeout || 8000;
this.retryDelay = options.retryDelay || 1000;
this.logger = options.logger || console;
}
addServiceTask(id, serviceCall, interval, options = {}) {
if (this.tasks.has(id)) {
this.logger.warn(`任务ID ${id} 已存在,将被替换`);
this.removeServiceTask(id);
}
const task = {
id,
serviceCall,
interval,
retryCount: options.retryCount || this.defaultRetry,
timeout: options.timeout || this.defaultTimeout,
onSuccess: options.onSuccess || (() => {}),
onError: options.onError || (() => {}),
onRetry: options.onRetry || (() => {}),
timerId: null,
lastRun: null,
nextRun: null,
errorCount: 0,
status: 'pending'
};
this.tasks.set(id, task);
this.scheduleTask(id);
return id;
}
scheduleTask(id) {
const task = this.tasks.get(id);
if (!task) return;
if (task.timerId) clearTimeout(task.timerId);
const now = Date.now();
const nextRun = task.lastRun ? task.lastRun + task.interval : now;
task.timerId = setTimeout(() => {
this.executeTask(id);
}, Math.max(0, nextRun - now));
task.nextRun = nextRun;
task.status = 'scheduled';
}
async executeTask(id) {
const task = this.tasks.get(id);
if (!task) return;
task.lastRun = Date.now();
task.status = 'running';
this.scheduleTask(id);
if (this.activeRequests >= this.maxConcurrent) {
this.taskQueue.push(id);
task.status = 'queued';
return;
}
this.activeRequests++;
let timeoutId;
try {
const controller = new AbortController();
timeoutId = setTimeout(() => controller.abort(), task.timeout);
task.status = 'executing';
const result = await task.serviceCall({ signal: controller.signal });
clearTimeout(timeoutId);
task.onSuccess(result);
task.errorCount = 0;
task.status = 'completed';
} catch (error) {
if (timeoutId) clearTimeout(timeoutId);
task.errorCount++;
if (task.errorCount <= task.retryCount) {
this.logger.warn(`任务 ${id} 失败,将重试 (${task.errorCount}/${task.retryCount})`);
task.onRetry(error, task.errorCount);
task.status = 'retrying';
// 使用指数退避策略
const backoffDelay = this.retryDelay * Math.pow(2, task.errorCount - 1);
setTimeout(() => this.executeTask(id), backoffDelay);
} else {
task.onError(error);
task.errorCount = 0;
task.status = 'failed';
}
} finally {
this.activeRequests--;
// 检查队列中等待的任务
if (this.taskQueue.length > 0 && this.activeRequests < this.maxConcurrent) {
const nextId = this.taskQueue.shift();
this.executeTask(nextId);
}
}
}
removeServiceTask(id) {
const task = this.tasks.get(id);
if (task && task.timerId) {
clearTimeout(task.timerId);
}
this.tasks.delete(id);
const index = this.taskQueue.indexOf(id);
if (index !== -1) this.taskQueue.splice(index, 1);
}
getTask(id) {
return this.tasks.get(id);
}
getAllTasks() {
return Array.from(this.tasks.values());
}
pauseAll() {
for (const task of this.tasks.values()) {
if (task.timerId) {
clearTimeout(task.timerId);
task.timerId = null;
task.status = 'paused';
}
}
}
resumeAll() {
for (const task of this.tasks.values()) {
if (!task.timerId) {
this.scheduleTask(task.id);
}
}
}
}
// 导出方式 1: CommonJS (Node.js)
if (typeof module !== 'undefined' && module.exports) {
module.exports = TimedRequestScheduler;
}
// 导出方式 2: ES6 模块
if (typeof exports !== 'undefined') {
exports.default = TimedRequestScheduler;
}
// 导出方式 3: 全局变量 (浏览器环境)
if (typeof window !== 'undefined') {
window.TimedRequestScheduler = TimedRequestScheduler;
}
```
## 使用示例
### 在 Node.js 中使用 (CommonJS)
```javascript
// app.js
const TimedRequestScheduler = require('./timed-request-scheduler');
// 创建调度器实例
const scheduler = new TimedRequestScheduler({
maxConcurrent: 5,
defaultRetry: 3
});
// 添加天气服务任务
scheduler.addServiceTask('weather', async ({ signal }) => {
const response = await fetch('https://api.weather.com/v1/current?city=beijing', { signal });
if (!response.ok) throw new Error('天气服务请求失败');
return response.json();
}, 60000, {
onSuccess: (data) => console.log(`当前温度: ${data.temperature}℃`),
onError: (err) => console.error('天气服务错误:', err.message),
retryCount: 5
});
// 添加股票服务任务
scheduler.addServiceTask('stock', async ({ signal }) => {
const response = await fetch('https://api.stock.com/v1/quote/AAPL', { signal });
if (!response.ok) throw new Error('股票服务请求失败');
return response.json();
}, 30000, {
onSuccess: (data) => console.log(`苹果股价: $${data.price}`),
onError: (err) => console.error('股票服务错误:', err.message)
});
// 10分钟后停止所有任务
setTimeout(() => {
scheduler.pauseAll();
console.log('所有任务已暂停');
}, 600000);
```
### 在浏览器中使用 (ES6 模块)
```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>定时任务调度器示例</title>
<script type="module">
import TimedRequestScheduler from './timed-request-scheduler.js';
// 创建调度器实例
const scheduler = new TimedRequestScheduler({
maxConcurrent: 2,
logger: {
log: console.log,
warn: console.warn,
error: console.error
}
});
// 添加用户数据服务
scheduler.addServiceTask('user-service', async ({ signal }) => {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1', { signal });
if (!response.ok) throw new Error('用户服务请求失败');
return response.json();
}, 5000, {
onSuccess: (data) => {
document.getElementById('user-data').innerHTML = `
<h3>用户信息</h3>
<p>姓名: ${data.name}</p>
<p>邮箱: ${data.email}</p>
`;
},
onError: (err) => console.error('用户服务错误:', err.message)
});
// 添加帖子数据服务
scheduler.addServiceTask('post-service', async ({ signal }) => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1', { signal });
if (!response.ok) throw new Error('帖子服务请求失败');
return response.json();
}, 7000, {
onSuccess: (data) => {
document.getElementById('post-data').innerHTML = `
<h3>帖子信息</h3>
<p>标题: ${data.title}</p>
<p>内容: ${data.body}</p>
`;
},
onError: (err) => console.error('帖子服务错误:', err.message)
});
// 添加任务状态监控
setInterval(() => {
const statusDiv = document.getElementById('task-status');
const tasks = scheduler.getAllTasks();
statusDiv.innerHTML = '<h3>任务状态监控</h3>';
tasks.forEach(task => {
statusDiv.innerHTML += `
<div class="task">
<strong>${task.id}</strong>:
<span>状态: ${task.status}</span>,
<span>下次运行: ${task.nextRun ? new Date(task.nextRun).toLocaleTimeString() : 'N/A'}</span>,
<span>错误计数: ${task.errorCount}</span>
</div>
`;
});
}, 1000);
</script>
<style>
.task { margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
#user-data, #post-data, #task-status { margin: 20px 0; padding: 15px; border: 1px solid #eee; }
</style>
</head>
<body>
<h1>定时任务调度器示例</h1>
<div id="user-data">加载用户数据中...</div>
<div id="post-data">加载帖子数据中...</div>
<div id="task-status">加载任务状态中...</div>
<button id="pause-btn">暂停所有任务</button>
<button id="resume-btn">恢复所有任务</button>
<script>
document.getElementById('pause-btn').addEventListener('click', () => {
scheduler.pauseAll();
alert('所有任务已暂停');
});
document.getElementById('resume-btn').addEventListener('click', () => {
scheduler.resumeAll();
alert('所有任务已恢复');
});
</script>
</body>
</html>
```
### 在浏览器中使用 (全局变量)
```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>定时任务调度器示例</title>
<script src="timed-request-scheduler.js"></script>
<script>
// 创建调度器实例
const scheduler = new TimedRequestScheduler();
// 添加服务任务
scheduler.addServiceTask('time-service', () => {
return new Promise(resolve => {
setTimeout(() => {
const now = new Date();
document.getElementById('current-time').textContent =
`当前时间: ${now.toLocaleTimeString()}`;
resolve(now);
}, 500);
});
}, 1000, {
onSuccess: (time) => console.log('时间更新成功:', time)
});
</script>
</head>
<body>
<h1>当前时间</h1>
<div id="current-time">加载中...</div>
</body>
</html>
```
## 关键功能说明
1. **灵活的导出方式**:
- 支持 CommonJS (Node.js)
- 支持 ES6 模块 (现代浏览器)
- 支持全局变量 (传统浏览器)
2. **配置选项**:
- `maxConcurrent`: 最大并发请求数
- `defaultRetry`: 默认重试次数
- `defaultTimeout`: 默认请求超时时间
- `retryDelay`: 重试延迟基数
- `logger`: 自定义日志记录器
3. **任务状态管理**:
- `pending`: 任务已添加但未调度
- `scheduled`: 任务已调度
- `queued`: 任务在队列中等待
- `running`: 任务开始执行
- `executing`: 请求正在进行
- `completed`: 任务成功完成
- `retrying`: 任务正在重试
- `failed`: 任务最终失败
- `paused`: 任务已暂停
4. **高级特性**:
- 指数退避重试策略
- 并发控制
- 任务队列管理
- 任务状态监控
- 暂停/恢复功能
## 使用场景建议
1. **实时数据仪表盘**:定期从多个API获取数据更新UI
2. **后台同步任务**:定期同步本地数据到服务器
3. **健康检查系统**:监控多个服务的可用性
4. **价格监控**:定期检查商品价格变化
5. **新闻聚合**:从多个来源定期获取最新新闻