Implement fetch polyfill and Promise for ES5 backend

Co-authored-by: qaiu <29825328+qaiu@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-06 22:44:52 +00:00
parent 5809d3b664
commit 5ffe94e3a4
6 changed files with 1005 additions and 1 deletions

View File

@@ -0,0 +1,105 @@
// ==UserScript==
// @name Fetch API示例解析器
// @type fetch_demo
// @displayName Fetch演示
// @description 演示如何在ES5环境中使用fetch API和async/await
// @match https?://example\.com/s/(?<KEY>\w+)
// @author QAIU
// @version 1.0.0
// ==/UserScript==
// 使用require导入类型定义仅用于IDE类型提示
var types = require('./types');
/** @typedef {types.ShareLinkInfo} ShareLinkInfo */
/** @typedef {types.JsHttpClient} JsHttpClient */
/** @typedef {types.JsLogger} JsLogger */
/**
* 演示使用fetch API的解析器
* 注意虽然源码中使用了ES6+语法async/await但在浏览器中会被编译为ES5
*
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息
* @param {JsHttpClient} http - HTTP客户端传统方式
* @param {JsLogger} logger - 日志对象
* @returns {string} 下载链接
*/
function parse(shareLinkInfo, http, logger) {
logger.info("=== Fetch API Demo ===");
// 方式1使用传统的http对象同步
logger.info("方式1: 使用传统http对象");
var response1 = http.get("https://httpbin.org/get");
logger.info("状态码: " + response1.statusCode());
// 方式2使用fetch API基于Promise
logger.info("方式2: 使用fetch API");
// 注意在ES5环境中我们需要手动处理Promise
// 这个示例展示了如何在ES5中使用fetch
var fetchPromise = fetch("https://httpbin.org/get");
// 等待Promise完成同步等待模拟
var result = null;
var error = null;
fetchPromise
.then(function(response) {
logger.info("Fetch响应状态: " + response.status);
return response.text();
})
.then(function(text) {
logger.info("Fetch响应内容: " + text.substring(0, 100) + "...");
result = "https://example.com/download/demo.file";
})
['catch'](function(err) {
logger.error("Fetch失败: " + err.message);
error = err;
});
// 简单的等待循环(实际场景中不推荐,这里仅作演示)
var timeout = 5000; // 5秒超时
var start = Date.now();
while (result === null && error === null && (Date.now() - start) < timeout) {
// 等待Promise完成
java.lang.Thread.sleep(10);
}
if (error !== null) {
throw error;
}
if (result === null) {
throw new Error("Fetch超时");
}
return result;
}
/**
* 演示POST请求
*/
function demonstratePost(logger) {
logger.info("=== 演示POST请求 ===");
var postPromise = fetch("https://httpbin.org/post", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
key: "value",
demo: true
})
});
postPromise
.then(function(response) {
return response.json();
})
.then(function(data) {
logger.info("POST响应: " + JSON.stringify(data));
})
['catch'](function(err) {
logger.error("POST失败: " + err.message);
});
}

View File

@@ -0,0 +1,299 @@
// ==FetchRuntime==
// @name Fetch API Polyfill for ES5
// @description Fetch API and Promise implementation for ES5 JavaScript engines
// @version 1.0.0
// @author QAIU
// ==============
/**
* Simple Promise implementation compatible with ES5
* Supports basic Promise functionality needed for fetch API
*/
function SimplePromise(executor) {
var state = 'pending';
var value;
var handlers = [];
var self = this;
function resolve(result) {
if (state !== 'pending') return;
state = 'fulfilled';
value = result;
handlers.forEach(handle);
handlers = [];
}
function reject(err) {
if (state !== 'pending') return;
state = 'rejected';
value = err;
handlers.forEach(handle);
handlers = [];
}
function handle(handler) {
if (state === 'pending') {
handlers.push(handler);
} else {
setTimeout(function() {
if (state === 'fulfilled' && typeof handler.onFulfilled === 'function') {
try {
var result = handler.onFulfilled(value);
if (result && typeof result.then === 'function') {
result.then(handler.resolve, handler.reject);
} else {
handler.resolve(result);
}
} catch (e) {
handler.reject(e);
}
}
if (state === 'rejected' && typeof handler.onRejected === 'function') {
try {
var result = handler.onRejected(value);
if (result && typeof result.then === 'function') {
result.then(handler.resolve, handler.reject);
} else {
handler.resolve(result);
}
} catch (e) {
handler.reject(e);
}
} else if (state === 'rejected' && !handler.onRejected) {
handler.reject(value);
}
}, 0);
}
}
this.then = function(onFulfilled, onRejected) {
return new SimplePromise(function(resolveNext, rejectNext) {
handle({
onFulfilled: onFulfilled,
onRejected: onRejected,
resolve: resolveNext,
reject: rejectNext
});
});
};
this['catch'] = function(onRejected) {
return this.then(null, onRejected);
};
this['finally'] = function(onFinally) {
return this.then(
function(value) {
return SimplePromise.resolve(onFinally()).then(function() {
return value;
});
},
function(reason) {
return SimplePromise.resolve(onFinally()).then(function() {
throw reason;
});
}
);
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
// Static methods
SimplePromise.resolve = function(value) {
if (value && typeof value.then === 'function') {
return value;
}
return new SimplePromise(function(resolve) {
resolve(value);
});
};
SimplePromise.reject = function(reason) {
return new SimplePromise(function(resolve, reject) {
reject(reason);
});
};
SimplePromise.all = function(promises) {
return new SimplePromise(function(resolve, reject) {
var results = [];
var remaining = promises.length;
if (remaining === 0) {
resolve(results);
return;
}
function handleResult(index, value) {
results[index] = value;
remaining--;
if (remaining === 0) {
resolve(results);
}
}
for (var i = 0; i < promises.length; i++) {
(function(index) {
var promise = promises[index];
if (promise && typeof promise.then === 'function') {
promise.then(
function(value) { handleResult(index, value); },
reject
);
} else {
handleResult(index, promise);
}
})(i);
}
});
};
SimplePromise.race = function(promises) {
return new SimplePromise(function(resolve, reject) {
for (var i = 0; i < promises.length; i++) {
var promise = promises[i];
if (promise && typeof promise.then === 'function') {
promise.then(resolve, reject);
} else {
resolve(promise);
}
}
});
};
// Make Promise global if not already defined
if (typeof Promise === 'undefined') {
var Promise = SimplePromise;
}
/**
* Response object that mimics the Fetch API Response
*/
function FetchResponse(jsHttpResponse) {
this._jsResponse = jsHttpResponse;
this.status = jsHttpResponse.statusCode();
this.ok = this.status >= 200 && this.status < 300;
this.statusText = this.ok ? 'OK' : 'Error';
this.headers = {
get: function(name) {
return jsHttpResponse.header(name);
},
has: function(name) {
return jsHttpResponse.header(name) !== null;
},
entries: function() {
var headerMap = jsHttpResponse.headers();
var entries = [];
for (var key in headerMap) {
if (headerMap.hasOwnProperty(key)) {
entries.push([key, headerMap[key]]);
}
}
return entries;
}
};
}
FetchResponse.prototype.text = function() {
var body = this._jsResponse.body();
return SimplePromise.resolve(body || '');
};
FetchResponse.prototype.json = function() {
var self = this;
return this.text().then(function(text) {
try {
return JSON.parse(text);
} catch (e) {
throw new Error('Invalid JSON: ' + e.message);
}
});
};
FetchResponse.prototype.arrayBuffer = function() {
var bytes = this._jsResponse.bodyBytes();
return SimplePromise.resolve(bytes);
};
FetchResponse.prototype.blob = function() {
// Blob not supported in ES5, return bytes
return this.arrayBuffer();
};
/**
* Fetch API implementation using JavaFetch bridge
* @param {string} url - Request URL
* @param {Object} options - Fetch options (method, headers, body, etc.)
* @returns {Promise<FetchResponse>}
*/
function fetch(url, options) {
return new SimplePromise(function(resolve, reject) {
try {
// Parse options
options = options || {};
var method = (options.method || 'GET').toUpperCase();
var headers = options.headers || {};
var body = options.body;
// Prepare request options for JavaFetch
var requestOptions = {
method: method,
headers: {}
};
// Convert headers to simple object
if (headers) {
if (typeof headers.forEach === 'function') {
// Headers object
headers.forEach(function(value, key) {
requestOptions.headers[key] = value;
});
} else if (typeof headers === 'object') {
// Plain object
for (var key in headers) {
if (headers.hasOwnProperty(key)) {
requestOptions.headers[key] = headers[key];
}
}
}
}
// Add body if present
if (body !== undefined && body !== null) {
if (typeof body === 'string') {
requestOptions.body = body;
} else if (typeof body === 'object') {
// Assume JSON
requestOptions.body = JSON.stringify(body);
if (!requestOptions.headers['Content-Type'] && !requestOptions.headers['content-type']) {
requestOptions.headers['Content-Type'] = 'application/json';
}
}
}
// Call JavaFetch bridge
var jsHttpResponse = JavaFetch.fetch(url, requestOptions);
// Create Response object
var response = new FetchResponse(jsHttpResponse);
resolve(response);
} catch (e) {
reject(e);
}
});
}
// Export for global use
if (typeof window !== 'undefined') {
window.fetch = fetch;
window.Promise = Promise;
} else if (typeof global !== 'undefined') {
global.fetch = fetch;
global.Promise = Promise;
}