Tampermonkey中GM_xmlhttpRequest实现多链接并发请求并条件终止
问题背景
在使用Tampermonkey编写用户脚本时,经常需要从多个URL获取数据。传统的顺序请求方式效率低下,而并发请求能显著提升性能。但有时我们需要在获取到符合条件的结果后立即停止其他正在进行的请求,避免不必要的网络开销。
解决方案概述
本文将介绍如何使用GM_xmlhttpRequest API实现以下功能:
- 对多个链接发起并发请求
- 监控每个请求的响应
- 当某个请求返回符合条件的数据时,立即停止其他请求
- 处理请求完成后的清理工作
核心实现思路
实现的关键在于维护一个请求状态管理器,记录所有请求的完成情况,并在满足终止条件时主动中止未完成的请求。
步骤分解
- 创建请求URL列表
- 初始化请求状态跟踪器
- 为每个URL发起GM_xmlhttpRequest
- 在响应回调中检查终止条件
- 满足条件时中止其他请求并执行回调
- 处理所有请求完成后的逻辑
完整代码实现
以下是实现多链接并发请求并条件终止的完整代码示例:
// ==UserScript==
// @name 多链接并发请求示例
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 演示如何使用GM_xmlhttpRequest实现多链接并发请求并在获取到符合条件的结果后停止后续请求
// @author You
// @match https://ippipp.com/*
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
'use strict';
// 要请求的URL列表
const urls = [
'https://api.ipipp.com/data1',
'https://api.ipipp.com/data2',
'https://api.ipipp.com/data3',
'https://api.ipipp.com/data4',
'https://api.ipipp.com/data5'
];
// 请求状态跟踪器
const requestTracker = {
completed: 0,
total: urls.length,
abortControllers: [],
shouldAbort: false,
successfulData: null
};
// 检查是否应该终止请求的条件函数
function checkTerminationCondition(data) {
// 这里定义你的终止条件,例如:
// 1. 找到特定关键词
// 2. 数据满足某种条件
// 3. 响应状态码为特定值
return data && data.status === 'success' && data.result && data.result.found;
}
// 中止所有未完成的请求
function abortAllRequests() {
requestTracker.abortControllers.forEach(controller => {
if (controller && controller.abort) {
controller.abort();
}
});
requestTracker.abortControllers = [];
}
// 处理成功的响应
function handleSuccess(response, url) {
console.log(`请求成功: ${url}`, response);
try {
const data = JSON.parse(response.responseText);
// 检查终止条件
if (checkTerminationCondition(data)) {
console.log('找到符合条件的结果,准备终止其他请求');
requestTracker.shouldAbort = true;
requestTracker.successfulData = data;
// 中止其他请求
abortAllRequests();
// 执行成功回调
onSuccessfulResult(data, url);
}
} catch (e) {
console.error(`解析响应失败: ${url}`, e);
}
}
// 处理失败的响应
function handleError(error, url) {
console.error(`请求失败: ${url}`, error);
}
// 请求完成回调
function onRequestComplete(url) {
requestTracker.completed++;
console.log(`请求完成: ${url}, 进度: ${requestTracker.completed}/${requestTracker.total}`);
// 检查是否所有请求都已完成
if (requestTracker.completed >= requestTracker.total && !requestTracker.successfulData) {
console.log('所有请求已完成,但未找到符合条件的结果');
onAllRequestsCompleted();
}
}
// 找到符合条件结果时的回调
function onSuccessfulResult(data, url) {
console.log('=== 找到符合条件的结果 ===');
console.log('来源URL:', url);
console.log('数据:', data);
// 在这里处理你的业务逻辑
// 例如:更新页面内容、显示通知等
alert(`成功从 ${url} 获取到目标数据!`);
}
// 所有请求完成时的回调
function onAllRequestsCompleted() {
console.log('=== 所有请求已完成 ===');
if (!requestTracker.successfulData) {
console.log('未找到符合条件的结果');
// 在这里处理未找到结果的业务逻辑
alert('未找到符合条件的结果');
}
}
// 发起单个请求
function makeRequest(url, index) {
// 创建中止控制器
const abortController = { abort: () => {} };
requestTracker.abortControllers[index] = abortController;
GM_xmlhttpRequest({
method: 'GET',
url: url,
timeout: 10000, // 10秒超时
// 请求头
headers: {
'Content-Type': 'application/json',
'User-Agent': 'Tampermonkey Script'
},
// 响应类型
responseType: 'text',
// 请求超时回调
ontimeout: function(response) {
handleError(new Error('请求超时'), url);
onRequestComplete(url);
},
// 请求错误回调
onerror: function(response) {
handleError(new Error(`HTTP错误: ${response.status}`), url);
onRequestComplete(url);
},
// 请求中止回调
onabort: function(response) {
console.log(`请求被中止: ${url}`);
onRequestComplete(url);
},
// 请求成功回调
onload: function(response) {
// 检查响应状态
if (response.status >= 200 && response.status < 300) {
handleSuccess(response, url);
} else {
handleError(new Error(`HTTP状态码: ${response.status}`), url);
}
onRequestComplete(url);
}
});
}
// 主函数:发起所有请求
function startConcurrentRequests() {
console.log(`开始并发请求 ${urls.length} 个URL`);
// 重置状态跟踪器
requestTracker.completed = 0;
requestTracker.total = urls.length;
requestTracker.abortControllers = [];
requestTracker.shouldAbort = false;
requestTracker.successfulData = null;
// 为每个URL发起请求
urls.forEach((url, index) => {
// 如果已经决定中止,不再发起新请求
if (requestTracker.shouldAbort) {
return;
}
makeRequest(url, index);
});
}
// 启动请求
startConcurrentRequests();
})();代码详解
1. 请求状态跟踪器
requestTracker对象用于跟踪所有请求的状态:
- completed: 已完成的请求数量
- total: 总请求数量
- abortControllers: 存储每个请求的中止控制器
- shouldAbort: 标记是否需要中止其他请求
- successfulData: 存储成功获取的数据
2. 条件检查函数
checkTerminationCondition函数是核心的条件判断逻辑,你可以根据实际需求修改这个函数:
function checkTerminationCondition(data) {
// 示例条件:检查数据中是否包含特定字段
return data && data.targetFound === true;
// 其他可能的条件:
// - 检查响应时间
// - 验证数据完整性
// - 匹配特定模式
}3. 请求管理
makeRequest函数为每个URL创建独立的请求,并设置了完整的生命周期回调:
- ontimeout: 处理请求超时
- onerror: 处理网络错误和HTTP错误
- onabort: 处理请求被中止的情况
- onload: 处理成功响应
4. 并发控制
通过以下机制实现并发控制:
- 同时发起所有请求,不等待前一个完成
- 使用状态跟踪器监控整体进度
- 条件满足时通过abortAllRequests中止所有未完成请求
关键注意事项
1. 跨域请求限制
GM_xmlhttpRequest不受同源策略限制,但仍需目标服务器允许跨域访问。如果遇到CORS问题,可能需要:
- 在@connect指令中声明目标域名
- 确保目标服务器支持CORS
- 或使用JSONP等其他技术
2. 错误处理
完善的错误处理机制包括:
- 网络超时处理
- HTTP错误状态码处理
- JSON解析错误处理
- 中止操作的清理工作
3. 性能优化
为了提升性能,可以考虑:
- 设置合理的超时时间
- 限制最大并发数(如果需要)
- 使用缓存避免重复请求
- 压缩传输数据
4. 内存管理
及时清理资源:
- 请求完成后移除中止控制器
- 避免内存泄漏
- 合理管理大型响应数据
扩展应用场景
这种模式适用于多种实际场景:
- 多源数据聚合:从多个API获取相同数据,取最快返回的有效结果
- 价格比较:同时从多个电商平台获取商品价格,找到最低价
- 可用性检测:检查多个服务器的在线状态,找到第一个可用的
- 内容搜索:在多个文档库中并行搜索,返回第一个匹配结果
总结
通过使用GM_xmlhttpRequest结合状态跟踪和条件检查机制,我们可以高效地实现多链接并发请求,并在获取到目标结果后立即终止其他请求。这种方法显著提升了数据获取效率,减少了不必要的网络开销。
在实际应用中,需要根据具体需求调整条件判断逻辑、错误处理策略和性能参数,以达到最佳效果。