// 1. TextDecoder - // 2. TextDecoder's stream option (Firefox 38 + - how to detect ?) - // 3. Promise.prototype.finally - // 4. fetch + // 5. Response.prototype.body + // 6. ReadableStream.prototype.getReader (no Firefox support yet) + // 7. AbortController (Firefox 57) + var s = ""; if (window.Uint8Array != null && window.TextDecoder != null) { var b = function (byte) { var x = new Uint8Array(1); x[0] = byte; return x; }; var textDecoder = new TextDecoder(); var chunk = textDecoder.decode(b(208), {stream: true}) + textDecoder.decode(b(176), {stream: true}); s = chunk; }; console.log('TextDecoder', typeof window.TextDecoder); console.log('TextDecoder+stream', chunk === 'а'); console.log('Promise#finally', window.Promise != null ? typeof window.Promise.prototype.finally : typeof undefined); console.log('fetch', typeof window.fetch); console.log('Response#body', window.Response != null && 'body' in window.Response.prototype); console.log('ReadableStream#getReader', window.ReadableStream != null ? typeof window.ReadableStream.prototype.getReader : typeof undefined); console.log('AbortController', typeof window.AbortController); // an example from https://fetch.spec.whatwg.org/ var url = 'https://matrixcalc.org/jstest/tests.php?events'; function consume(reader) { var responseText = ''; var pump = function () { return reader.read().then(function (result) { if (result.done) { console.log('responseText', responseText); return; } responseText += String.fromCharCode.apply(undefined, result.value); return pump(); }); }; return pump(); } if (window.fetch != null && window.TextDecoder != null) { fetch(url).then(function (response) { var body = response.body; console.log('response.body', typeof body); if (body != null) { console.log('response.body.getReader', typeof body.getReader); if (typeof body.getReader == 'function') { consume(body.getReader()); } } }); } setTimeout(function () { console.log('done'); }, 1000); // Firefox < 38 (no "stream" option), IE, Edge function TextDecoderPolyfill() { } //TODO: streaming TextDecoderPolyfill.prototype.decode = function (octets) { var string = ""; var i = 0; while (i < octets.length) { var octet = octets[i]; var bytesNeeded = 0; var codePoint = 0; if (octet <= 0x7F) { bytesNeeded = 0; codePoint = octet & 0xFF; } else if (octet <= 0xDF) { bytesNeeded = 1; codePoint = octet & 0x1F; } else if (octet <= 0xEF) { bytesNeeded = 2; codePoint = octet & 0x0F; } else if (octet <= 0xF4) { bytesNeeded = 3; codePoint = octet & 0x07; } if (octets.length - i - bytesNeeded > 0) { var k = 0; while (k < bytesNeeded) { octet = octets[i + k + 1]; codePoint = (codePoint << 6) | (octet & 0x3F); k += 1; } } else { codePoint = 0xFFFD; bytesNeeded = octets.length - i; } string += String.fromCodePoint(codePoint); i += bytesNeeded + 1; } return string }; // ? if (Promise != undefined && Promise.prototype["finally"] == undefined) { Promise.prototype["finally"] = function (callback) { return this.then(function (result) { return Promise.resolve(callback()).then(function () { return result; }); }, function (error) { return Promise.resolve(callback()).then(function () { throw error; }); }); }; } var open = function (onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers, timeout) { // cache: "no-store" // https://bugs.chromium.org/p/chromium/issues/detail?id=453190 var textDecoder = new TextDecoder(); fetch(url, { headers: headers, credentials: withCredentials ? "include" : "same-origin" }).then(function (response) { var reader = response.body.getReader(); onStartCallback(response.status, response.statusText, response.headers.get("Content-Type")); return new Promise(function (resolve, reject) { var readNextChunk = function () { var id = setTimeout(function () { reader.cancel(); resolve(undefined); }, timeout); reader.read().then(function (result) { clearTimeout(id); if (result.done) { //Note: bytes in textDecoder are ignored resolve(undefined); } else { var chunk = textDecoder.decode(result.value, {stream: true}); //var chunk = String.fromCharCode.apply(undefined, result.value); onProgressCallback(chunk); readNextChunk(); } })["catch"](function (error) { clearTimeout(id); reject(error); }); }; readNextChunk(); }); })["finally"](function () { onFinishCallback(); }); };