mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-04-11 11:26:55 +00:00
Implement fetch polyfill and Promise for ES5 backend
Co-authored-by: qaiu <29825328+qaiu@users.noreply.github.com>
This commit is contained in:
105
parser/src/main/resources/custom-parsers/fetch-demo.js
Normal file
105
parser/src/main/resources/custom-parsers/fetch-demo.js
Normal 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);
|
||||
});
|
||||
}
|
||||
299
parser/src/main/resources/fetch-runtime.js
Normal file
299
parser/src/main/resources/fetch-runtime.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user