polyfills.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /**
  2. * polyfills.js — IE11 及老浏览器兼容垫片(ES5,离线内置,无外部依赖)
  3. * 提供: Promise / fetch / EventSource / padStart / startsWith / endsWith / includes
  4. * 注: 仅在浏览器缺少对应能力时才生效,现代浏览器加载本文件无任何副作用。
  5. */
  6. /* ---------------- String / Array 补齐 ---------------- */
  7. if (!String.prototype.padStart) {
  8. String.prototype.padStart = function (targetLength, padString) {
  9. targetLength = targetLength >> 0;
  10. if (this.length >= targetLength) return String(this);
  11. padString = padString === undefined ? " " : String(padString);
  12. var fill = "";
  13. while (fill.length < targetLength - this.length) fill += padString;
  14. return fill.slice(0, targetLength - this.length) + String(this);
  15. };
  16. }
  17. if (!String.prototype.startsWith) {
  18. String.prototype.startsWith = function (search, pos) {
  19. pos = pos || 0;
  20. return this.substr(pos, search.length) === String(search);
  21. };
  22. }
  23. if (!String.prototype.endsWith) {
  24. String.prototype.endsWith = function (search, endPos) {
  25. if (endPos === undefined || endPos > this.length) endPos = this.length;
  26. return this.substring(endPos - search.length, endPos) === String(search);
  27. };
  28. }
  29. if (!String.prototype.includes) {
  30. String.prototype.includes = function (search, start) {
  31. if (start === undefined) start = 0;
  32. return this.indexOf(String(search), start) !== -1;
  33. };
  34. }
  35. if (!Array.prototype.includes) {
  36. Array.prototype.includes = function (search) {
  37. for (var i = 0; i < this.length; i++) {
  38. if (this[i] === search) return true;
  39. }
  40. return false;
  41. };
  42. }
  43. if (!Object.assign) {
  44. Object.assign = function (target) {
  45. if (target === null || target === undefined) {
  46. throw new TypeError("Cannot convert undefined or null to object");
  47. }
  48. var to = Object(target);
  49. for (var i = 1; i < arguments.length; i++) {
  50. var src = arguments[i];
  51. if (src !== null && src !== undefined) {
  52. for (var k in src) {
  53. if (Object.prototype.hasOwnProperty.call(src, k)) to[k] = src[k];
  54. }
  55. }
  56. }
  57. return to;
  58. };
  59. }
  60. /* ---------------- Promise 补齐 (含 finally) ---------------- */
  61. if (typeof Promise !== "function") {
  62. function PromisePolyfill(executor) {
  63. var self = this;
  64. this._state = "pending";
  65. this._value = undefined;
  66. this._callbacks = [];
  67. function resolve(value) {
  68. settle(self, "fulfilled", value);
  69. }
  70. function reject(reason) {
  71. settle(self, "rejected", reason);
  72. }
  73. function settle(p, state, value) {
  74. if (p._state !== "pending") return;
  75. p._state = state;
  76. p._value = value;
  77. setTimeout(function () {
  78. flush(p);
  79. }, 0);
  80. }
  81. function flush(p) {
  82. var cbs = p._callbacks;
  83. p._callbacks = [];
  84. for (var i = 0; i < cbs.length; i++) handle(p, cbs[i]);
  85. }
  86. try {
  87. executor(resolve, reject);
  88. } catch (e) {
  89. reject(e);
  90. }
  91. }
  92. function handle(p, cb) {
  93. if (p._state === "pending") {
  94. p._callbacks.push(cb);
  95. return;
  96. }
  97. var state = p._state,
  98. value = p._value;
  99. var thenFn = state === "fulfilled" ? cb.onFulfilled : cb.onRejected;
  100. if (typeof thenFn !== "function") {
  101. (state === "fulfilled" ? cb.resolve : cb.reject)(value);
  102. return;
  103. }
  104. var called = false;
  105. try {
  106. var result = thenFn(value);
  107. if (
  108. result &&
  109. (typeof result === "object" || typeof result === "function") &&
  110. typeof result.then === "function"
  111. ) {
  112. result.then(cb.resolve, cb.reject);
  113. } else {
  114. cb.resolve(result);
  115. }
  116. } catch (e) {
  117. if (!called) cb.reject(e);
  118. called = true;
  119. }
  120. }
  121. function resolvePromise(p, x, resolve, reject) {
  122. var called = false;
  123. try {
  124. var then = x && x.then;
  125. if (
  126. x !== null &&
  127. (typeof x === "object" || typeof x === "function") &&
  128. typeof then === "function"
  129. ) {
  130. then.call(
  131. x,
  132. function (y) {
  133. if (called) return;
  134. called = true;
  135. resolvePromise(p, y, resolve, reject);
  136. },
  137. function (r) {
  138. if (called) return;
  139. called = true;
  140. reject(r);
  141. }
  142. );
  143. } else {
  144. resolve(x);
  145. }
  146. } catch (e) {
  147. if (!called) reject(e);
  148. }
  149. }
  150. PromisePolyfill.prototype.then = function (onFulfilled, onRejected) {
  151. var self = this;
  152. return new PromisePolyfill(function (resolve, reject) {
  153. handle(self, {
  154. onFulfilled: onFulfilled,
  155. onRejected: onRejected,
  156. resolve: resolve,
  157. reject: reject,
  158. });
  159. });
  160. };
  161. PromisePolyfill.prototype.catch = function (onRejected) {
  162. return this.then(null, onRejected);
  163. };
  164. PromisePolyfill.prototype.finally = function (fn) {
  165. var C = this.constructor;
  166. function wrap() {
  167. return C.resolve(fn && fn()).then(function () {
  168. return;
  169. });
  170. }
  171. return this.then(wrap, wrap);
  172. };
  173. PromisePolyfill.resolve = function (value) {
  174. if (value instanceof PromisePolyfill) return value;
  175. return new PromisePolyfill(function (resolve) {
  176. resolve(value);
  177. });
  178. };
  179. PromisePolyfill.reject = function (reason) {
  180. return new PromisePolyfill(function (resolve, reject) {
  181. reject(reason);
  182. });
  183. };
  184. PromisePolyfill.all = function (list) {
  185. return new PromisePolyfill(function (resolve, reject) {
  186. var results = [],
  187. remaining = list.length;
  188. if (!remaining) return resolve(results);
  189. list.forEach(function (item, i) {
  190. PromisePolyfill.resolve(item).then(function (v) {
  191. results[i] = v;
  192. if (--remaining === 0) resolve(results);
  193. }, reject);
  194. });
  195. });
  196. };
  197. window.Promise = PromisePolyfill;
  198. } else if (typeof Promise.prototype.finally !== "function") {
  199. Promise.prototype.finally = function (fn) {
  200. var C = this.constructor;
  201. function wrap() {
  202. return C.resolve(fn && fn()).then(function () {});
  203. }
  204. return this.then(wrap, wrap);
  205. };
  206. }
  207. /* ---------------- fetch 补齐 (XHR 实现) ---------------- */
  208. if (typeof window.fetch !== "function") {
  209. function FetchResponse(xhr) {
  210. this.ok = xhr.status >= 200 && xhr.status < 300;
  211. this.status = xhr.status;
  212. this.statusText = xhr.statusText;
  213. this.url = xhr.responseURL || "";
  214. this._raw = xhr;
  215. var raw = xhr;
  216. this.headers = {
  217. get: function (name) {
  218. var v = raw.getResponseHeader(name);
  219. return v === null ? null : String(v);
  220. },
  221. };
  222. }
  223. FetchResponse.prototype.text = function () {
  224. return Promise.resolve(this._raw.responseText);
  225. };
  226. FetchResponse.prototype.json = function () {
  227. var raw = this._raw;
  228. return Promise.resolve().then(function () {
  229. return JSON.parse(raw.responseText);
  230. });
  231. };
  232. FetchResponse.prototype.blob = function () {
  233. var raw = this._raw;
  234. return Promise.resolve().then(function () {
  235. var type = raw.getResponseHeader("Content-Type") || "application/octet-stream";
  236. return new Blob([raw.responseText], { type: String(type) });
  237. });
  238. };
  239. window.fetch = function (input, init) {
  240. init = init || {};
  241. var url = typeof input === "string" ? input : String(input.url || input);
  242. return new Promise(function (resolve, reject) {
  243. var xhr = new XMLHttpRequest();
  244. xhr.open(init.method || "GET", url, true);
  245. if (init.headers) {
  246. var h = init.headers;
  247. if (typeof h.forEach === "function") {
  248. h.forEach(function (v, k) {
  249. xhr.setRequestHeader(k, v);
  250. });
  251. } else {
  252. for (var k in h) {
  253. if (Object.prototype.hasOwnProperty.call(h, k)) {
  254. xhr.setRequestHeader(k, h[k]);
  255. }
  256. }
  257. }
  258. }
  259. xhr.timeout = init.timeout || 0;
  260. xhr.onload = function () {
  261. resolve(new FetchResponse(xhr));
  262. };
  263. xhr.onerror = function () {
  264. reject(new TypeError("Network request failed"));
  265. };
  266. xhr.ontimeout = function () {
  267. reject(new TypeError("Request timed out"));
  268. };
  269. xhr.send(init.body === undefined ? null : init.body);
  270. });
  271. };
  272. }
  273. /* ---------------- EventSource (SSE) 补齐 (XHR 流式实现) ---------------- */
  274. if (typeof window.EventSource !== "function") {
  275. var ES_CONNECTING = 0,
  276. ES_OPEN = 1,
  277. ES_CLOSED = 2;
  278. function EventSourcePolyfill(url) {
  279. var self = this;
  280. this.url = url;
  281. this.readyState = ES_CONNECTING;
  282. this.onopen = null;
  283. this.onmessage = null;
  284. this.onerror = null;
  285. this._xhr = null;
  286. this._processed = 0;
  287. this._buf = "";
  288. this._closed = false;
  289. this._connect();
  290. }
  291. EventSourcePolyfill.prototype._fire = function (type, data) {
  292. var handler = this["on" + type];
  293. if (typeof handler !== "function") return;
  294. var evt;
  295. try {
  296. evt = new MessageEvent(type, { data: data });
  297. } catch (e) {
  298. evt = document.createEvent("MessageEvent");
  299. evt.initMessageEvent(type, false, false, data, "", "", window, []);
  300. }
  301. handler.call(this, evt);
  302. };
  303. EventSourcePolyfill.prototype._connect = function () {
  304. var self = this;
  305. var xhr = new XMLHttpRequest();
  306. this._xhr = xhr;
  307. this._processed = 0;
  308. this._buf = "";
  309. xhr.open("GET", this.url, true);
  310. xhr.setRequestHeader("Accept", "text/event-stream");
  311. xhr.setRequestHeader("Cache-Control", "no-cache");
  312. if (xhr.overrideMimeType) xhr.overrideMimeType("text/plain; charset=x-user-defined");
  313. var opened = false;
  314. function parseChunk(text) {
  315. // 以空行分隔事件块
  316. var blocks = self._buf.split("\n\n");
  317. self._buf = blocks.pop();
  318. blocks.forEach(function (block) {
  319. var dataLines = [];
  320. block.split("\n").forEach(function (line) {
  321. if (line.indexOf("data:") === 0) {
  322. dataLines.push(line.charAt(5) === " " ? line.slice(6) : line.slice(5));
  323. }
  324. // event:/id:/retry: 当前业务未使用, 忽略
  325. });
  326. if (dataLines.length) {
  327. self._fire("message", dataLines.join("\n"));
  328. }
  329. });
  330. }
  331. xhr.onprogress = function () {
  332. if (!opened) {
  333. opened = true;
  334. self.readyState = ES_OPEN;
  335. self._fire("open", undefined);
  336. }
  337. var full = xhr.responseText;
  338. if (full.length > self._processed) {
  339. self._buf += full.slice(self._processed);
  340. self._processed = full.length;
  341. parseChunk();
  342. }
  343. };
  344. xhr.onload = xhr.onprogress;
  345. xhr.onerror = function () {
  346. if (self._closed) return;
  347. self.readyState = ES_CONNECTING;
  348. self._fire("error", undefined);
  349. // 简单重连策略: 关闭后由上层重新创建; 此处自动重连
  350. if (self._retryTimer) clearTimeout(self._retryTimer);
  351. self._retryTimer = setTimeout(function () {
  352. if (!self._closed && self.readyState !== ES_CLOSED) self._connect();
  353. }, 3000);
  354. };
  355. try {
  356. xhr.send(null);
  357. } catch (e) {
  358. setTimeout(function () {
  359. self._fire("error", undefined);
  360. }, 0);
  361. }
  362. };
  363. EventSourcePolyfill.prototype.close = function () {
  364. this._closed = true;
  365. this.readyState = ES_CLOSED;
  366. if (this._xhr) {
  367. try {
  368. this._xhr.abort();
  369. } catch (e) {}
  370. }
  371. if (this._retryTimer) {
  372. clearTimeout(this._retryTimer);
  373. this._retryTimer = null;
  374. }
  375. };
  376. window.EventSource = EventSourcePolyfill;
  377. }
  378. /* ---------------- 文件下载兜底 (IE11 不支持 a[download]) ---------------- */
  379. /**
  380. * 统一文件下载入口: 现代浏览器走 a[download], IE10/11 走 msSaveBlob
  381. * @param {Blob} blob
  382. * @param {string} filename
  383. */
  384. function downloadBlobFile(blob, filename) {
  385. if (window.navigator && window.navigator.msSaveOrOpenBlob) {
  386. window.navigator.msSaveOrOpenBlob(blob, filename);
  387. return;
  388. }
  389. var url = URL.createObjectURL(blob);
  390. var a = document.createElement("a");
  391. a.href = url;
  392. a.download = filename || "download";
  393. document.body.appendChild(a);
  394. a.click();
  395. setTimeout(function () {
  396. document.body.removeChild(a);
  397. URL.revokeObjectURL(url);
  398. }, 100);
  399. }