ScriptCat异步API兼容性终极指南:告别GM.xmlHttpRequest卡顿问题
ScriptCat异步API兼容性终极指南:告别GM.xmlHttpRequest卡顿问题
【免费下载链接】scriptcatScriptCat, a browser extension that can execute userscript; 脚本猫,一个可以执行用户脚本的浏览器扩展项目地址: https://gitcode.com/gh_mirrors/sc/scriptcat
ScriptCat作为一款现代化的用户脚本管理器,始终致力于提供最流畅的异步API体验。在最新的版本中,我们彻底解决了GM.xmlHttpRequest的异步兼容性问题,确保您的脚本能够在Tampermonkey、Violentmonkey和ScriptCat之间无缝迁移。本文将深入解析异步API的核心实现,并提供实用的使用指南。
作为一款功能强大的浏览器扩展,ScriptCat支持完整的GM.* API规范,让开发者能够编写跨平台的用户脚本。通过优化异步处理机制,ScriptCat现在能够完美支持await GM.xmlHttpRequest()语法,彻底告别数据请求卡顿问题。
🚀 异步API的核心优势
ScriptCat的异步API设计遵循现代JavaScript最佳实践,确保您的脚本能够充分利用Promise带来的便利:
- 真正的Promise支持- 所有GM.* API都返回标准的Promise对象
- 无缝await语法- 可以直接使用
await等待异步操作完成 - 错误处理优化- 提供完整的try-catch错误处理机制
- 性能提升- 避免回调地狱,代码结构更清晰
📊 GM.xmlHttpRequest异步使用对比
让我们通过一个简单的示例来展示新旧API的区别:
// ❌ 传统回调方式(易出错) GM.xmlHttpRequest({ method: "GET", url: "https://api.github.com/repos/scriptscat/scriptcat", onload: function(response) { if (response.status === 200) { const data = JSON.parse(response.responseText); console.log("仓库名称:", data.name); } }, onerror: function(error) { console.error("请求失败:", error); } }); // ✅ ScriptCat异步方式(推荐) try { const response = await GM.xmlHttpRequest({ method: "GET", url: "https://api.github.com/repos/scriptscat/scriptcat", timeout: 10000 }); if (response.status === 200) { const data = JSON.parse(response.responseText); console.log("仓库名称:", data.name); } } catch (error) { console.error("请求失败:", error); }图:ScriptCat异步API请求流程示意图,展示了从发起请求到获取响应的完整异步处理过程
🔧 核心源码实现解析
ScriptCat的异步API实现位于src/app/service/content/gm_api/gm_api.ts中,核心代码如下:
@GMContext.API() public "GM.xmlHttpRequest"(details: GMTypes.XHRDetails): Promise<GMTypes.XHRResponse> & GMRequestHandle { const { retPromise, abort } = GM_xmlhttpRequest(this, details, true); const ret = retPromise as Promise<GMTypes.XHRResponse> & GMRequestHandle; ret.abort = abort; return ret; }这个实现确保了:
- 返回标准的Promise对象
- 同时支持
abort()方法中断请求 - 保持与Tampermonkey API的完全兼容
🧪 完整的异步API测试套件
在example/tests/gm_api_async_test.js中,我们提供了全面的异步API测试示例:
// 异步存储API测试 await testAsync("GM.setValue - 字符串", async () => { await GM.setValue("test_string", "Hello ScriptCat Async"); const value = await GM.getValue("test_string"); assert("Hello ScriptCat Async", value, "GM.getValue应该返回正确的字符串值"); }); // 异步网络请求测试 await testAsync("GM.xmlHttpRequest - GET请求", async () => { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: "GET", url: "https://api.github.com/repos/scriptscat/scriptcat", timeout: 10000, onload: (response) => { assert(200, response.status, `请求状态码应该是200`); resolve(); }, onerror: (error) => reject(new Error("请求失败: " + error)) }); }); });💡 三步配置异步脚本开发环境
第1步:启用异步支持
确保您的脚本头部包含正确的元数据声明:
// ==UserScript== // @name 异步脚本示例 // @namespace https://docs.scriptcat.org/ // @version 1.0.0 // @grant GM.xmlHttpRequest // @grant GM.getValue // @grant GM.setValue // ==/UserScript==第2步:使用async函数包装
将主要逻辑包装在async函数中:
(async function() { "use strict"; try { // 异步获取数据 const data = await fetchData(); // 处理数据 processData(data); } catch (error) { console.error("脚本执行失败:", error); } })(); async function fetchData() { const response = await GM.xmlHttpRequest({ method: "GET", url: "https://api.example.com/data", timeout: 5000 }); return JSON.parse(response.responseText); }第3步:错误处理最佳实践
async function safeRequest(url) { try { const response = await GM.xmlHttpRequest({ method: "GET", url: url, timeout: 10000 }); if (response.status >= 200 && response.status < 300) { return response.responseText; } else { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } } catch (error) { // 重试逻辑 console.warn(`请求失败,3秒后重试: ${error.message}`); await new Promise(resolve => setTimeout(resolve, 3000)); return safeRequest(url); // 递归重试 } }🛠️ 高效调试异步脚本技巧
控制台日志增强
// 创建带时间戳的日志函数 const log = { info: (...args) => console.log(`[${new Date().toISOString()}]`, ...args), error: (...args) => console.error(`[${new Date().toISOString()}]`, ...args), warn: (...args) => console.warn(`[${new Date().toISOString()}]`, ...args) }; // 使用示例 log.info("开始异步请求"); const response = await GM.xmlHttpRequest({ /* ... */ }); log.info("请求完成,状态码:", response.status);性能监控
async function measureRequest(url) { const startTime = performance.now(); try { const response = await GM.xmlHttpRequest({ method: "GET", url: url }); const endTime = performance.now(); const duration = endTime - startTime; console.log(`请求 ${url} 耗时: ${duration.toFixed(2)}ms`); return response; } catch (error) { console.error(`请求 ${url} 失败:`, error); throw error; } }📈 实际应用案例:数据抓取脚本
以下是一个完整的异步数据抓取脚本示例:
// ==UserScript== // @name 异步数据抓取器 // @namespace https://docs.scriptcat.org/ // @version 1.0.0 // @grant GM.xmlHttpRequest // @grant GM.getValue // @grant GM.setValue // @grant GM.notification // @match https://example.com/* // ==/UserScript== (async function() { 'use strict'; class DataFetcher { constructor() { this.cacheKey = 'data_fetcher_cache'; this.maxRetries = 3; } async fetchWithRetry(url, options = {}) { for (let attempt = 1; attempt <= this.maxRetries; attempt++) { try { console.log(`第${attempt}次尝试请求: ${url}`); const response = await GM.xmlHttpRequest({ method: options.method || 'GET', url: url, timeout: options.timeout || 10000, headers: options.headers || {}, ...options }); if (response.status === 200) { return response.responseText; } else { throw new Error(`HTTP ${response.status}`); } } catch (error) { if (attempt === this.maxRetries) { throw error; } // 指数退避重试 await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000) ); } } } async fetchAndProcess() { try { // 从缓存读取上次获取时间 const lastFetch = await GM.getValue('last_fetch_time', 0); const now = Date.now(); // 如果距离上次获取超过1小时,重新获取 if (now - lastFetch > 3600000) { console.log('缓存过期,重新获取数据'); const data = await this.fetchWithRetry( 'https://api.example.com/latest-data' ); // 处理数据 const processed = this.processData(data); // 保存到缓存 await GM.setValue('fetched_data', processed); await GM.setValue('last_fetch_time', now); // 发送通知 await GM.notification({ title: '数据更新成功', text: `成功获取${processed.length}条数据`, timeout: 5000 }); return processed; } else { console.log('使用缓存数据'); return await GM.getValue('fetched_data', []); } } catch (error) { console.error('数据获取失败:', error); // 发送错误通知 await GM.notification({ title: '数据获取失败', text: error.message, timeout: 10000 }); throw error; } } processData(rawData) { // 数据处理逻辑 return JSON.parse(rawData).map(item => ({ id: item.id, name: item.name, timestamp: new Date().toISOString() })); } } // 主执行逻辑 const fetcher = new DataFetcher(); // 等待页面加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', async () => { await fetcher.fetchAndProcess(); }); } else { await fetcher.fetchAndProcess(); } })();🔍 常见问题排查指南
问题1:Promise未正确返回
症状:await GM.xmlHttpRequest()立即返回,不等待请求完成解决方案:确保使用最新版ScriptCat,检查脚本头部是否包含正确的@grant声明
问题2:请求被取消
症状:请求在特定条件下被意外中断解决方案:检查超时设置,确保网络连接稳定,使用try-catch包装请求
问题3:跨域请求失败
症状:控制台显示CORS错误解决方案:在脚本头部添加@connect指令,声明允许访问的域名
🎯 总结与最佳实践
ScriptCat的异步API改进为开发者带来了显著的便利:
- 代码更简洁- 使用async/await替代回调嵌套
- 错误处理更完善- 统一的try-catch机制
- 性能更优秀- 避免回调地狱,提升执行效率
- 兼容性更好- 与主流用户脚本管理器保持API一致
无论您是开发新的用户脚本,还是迁移现有脚本到ScriptCat,现在都可以放心使用标准的异步编程模式。ScriptCat的异步API实现确保了最佳的开发体验和运行时性能。
通过遵循本文的指南和最佳实践,您将能够充分利用ScriptCat的强大功能,构建出更加健壮、高效的用户脚本。立即开始体验ScriptCat带来的现代化异步编程体验吧!
【免费下载链接】scriptcatScriptCat, a browser extension that can execute userscript; 脚本猫,一个可以执行用户脚本的浏览器扩展项目地址: https://gitcode.com/gh_mirrors/sc/scriptcat
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
