// ==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) { if (promises.length === 0) { // Per spec, Promise.race with empty array stays pending forever return; } 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); return; } } }); }; // 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; // Map HTTP status codes to standard status text var statusTexts = { 200: 'OK', 201: 'Created', 204: 'No Content', 301: 'Moved Permanently', 302: 'Found', 304: 'Not Modified', 400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden', 404: 'Not Found', 405: 'Method Not Allowed', 408: 'Request Timeout', 409: 'Conflict', 410: 'Gone', 500: 'Internal Server Error', 501: 'Not Implemented', 502: 'Bad Gateway', 503: 'Service Unavailable', 504: 'Gateway Timeout' }; this.statusText = statusTexts[this.status] || (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} */ 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; }