scheduler.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /**
  2. * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. /**
  7. * setImmediate polyfill adapted from https://github.com/YuzuJS/setImmediate
  8. * Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola
  9. * MIT license.
  10. */
  11. declare const WorkerGlobalScope: any;
  12. function createImmediateActions() {
  13. const global: any = (function () {
  14. const _window = typeof window !== 'undefined' && window;
  15. const _self = typeof self !== 'undefined' && typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope && self;
  16. const _global = typeof global !== 'undefined' && global;
  17. return _window || _global || _self;
  18. })();
  19. type Callback = (...args: any[]) => void;
  20. type Task = { callback: Callback, args: any[] }
  21. const tasksByHandle: { [handle: number]: Task } = { };
  22. const doc = typeof document !== 'undefined' ? document : void 0;
  23. let nextHandle = 1; // Spec says greater than zero
  24. let registerImmediate: ((handle: number) => void);
  25. function setImmediate(callback: Callback, ...args: any[]) {
  26. // Callback can either be a function or a string
  27. if (typeof callback !== 'function') {
  28. callback = new Function('' + callback) as Callback;
  29. }
  30. // Store and register the task
  31. const task = { callback: callback, args: args };
  32. tasksByHandle[nextHandle] = task;
  33. registerImmediate(nextHandle);
  34. return nextHandle++;
  35. }
  36. function clearImmediate(handle: number) {
  37. delete tasksByHandle[handle];
  38. }
  39. function run(task: Task) {
  40. const callback = task.callback;
  41. const args = task.args;
  42. switch (args.length) {
  43. case 0:
  44. callback();
  45. break;
  46. case 1:
  47. callback(args[0]);
  48. break;
  49. case 2:
  50. callback(args[0], args[1]);
  51. break;
  52. case 3:
  53. callback(args[0], args[1], args[2]);
  54. break;
  55. default:
  56. callback.apply(undefined, args);
  57. break;
  58. }
  59. }
  60. function runIfPresent(handle: number) {
  61. const task = tasksByHandle[handle];
  62. clearImmediate(handle);
  63. run(task);
  64. }
  65. function installNextTickImplementation() {
  66. registerImmediate = function(handle) {
  67. process.nextTick(function () { runIfPresent(handle); });
  68. };
  69. }
  70. function canUsePostMessage() {
  71. if (global && global.postMessage && !global.importScripts) {
  72. let postMessageIsAsynchronous = true;
  73. const oldOnMessage = global.onmessage;
  74. global.onmessage = function() {
  75. postMessageIsAsynchronous = false;
  76. };
  77. global.postMessage('', '*');
  78. global.onmessage = oldOnMessage;
  79. return postMessageIsAsynchronous;
  80. }
  81. }
  82. function installPostMessageImplementation() {
  83. // Installs an event handler on `global` for the `message` event: see
  84. // * https://developer.mozilla.org/en/DOM/window.postMessage
  85. // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages
  86. const messagePrefix = 'setImmediate$' + Math.random() + '$';
  87. const onGlobalMessage = function(event: any) {
  88. if (event.source === global &&
  89. typeof event.data === 'string' &&
  90. event.data.indexOf(messagePrefix) === 0) {
  91. runIfPresent(+event.data.slice(messagePrefix.length));
  92. }
  93. };
  94. if (window.addEventListener) {
  95. window.addEventListener('message', onGlobalMessage, false);
  96. } else {
  97. (window as any).attachEvent('onmessage', onGlobalMessage);
  98. }
  99. registerImmediate = function(handle) {
  100. window.postMessage(messagePrefix + handle, '*');
  101. };
  102. }
  103. function installMessageChannelImplementation() {
  104. const channel = new MessageChannel();
  105. channel.port1.onmessage = function(event) {
  106. const handle = event.data;
  107. runIfPresent(handle);
  108. };
  109. registerImmediate = function(handle) {
  110. channel.port2.postMessage(handle);
  111. };
  112. }
  113. function installReadyStateChangeImplementation() {
  114. const html = doc!.documentElement!;
  115. registerImmediate = function(handle) {
  116. // Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
  117. // into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
  118. let script = doc!.createElement('script') as any;
  119. script.onreadystatechange = function () {
  120. runIfPresent(handle);
  121. script.onreadystatechange = null;
  122. html.removeChild(script);
  123. script = null;
  124. };
  125. html.appendChild(script);
  126. };
  127. }
  128. function installSetTimeoutImplementation() {
  129. registerImmediate = function(handle) {
  130. setTimeout(runIfPresent, 0, handle);
  131. };
  132. }
  133. // Don't get fooled by e.g. browserify environments.
  134. if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
  135. // For Node.js before 0.9
  136. installNextTickImplementation();
  137. } else if (canUsePostMessage()) {
  138. // For non-IE10 modern browsers
  139. installPostMessageImplementation();
  140. } else if (typeof MessageChannel !== 'undefined') {
  141. // For web workers, where supported
  142. installMessageChannelImplementation();
  143. } else if (doc && 'onreadystatechange' in doc.createElement('script')) {
  144. // For IE 6–8
  145. installReadyStateChangeImplementation();
  146. } else {
  147. // For older browsers
  148. installSetTimeoutImplementation();
  149. }
  150. return {
  151. setImmediate,
  152. clearImmediate
  153. };
  154. }
  155. const immediateActions = (function () {
  156. if (typeof setImmediate !== 'undefined') {
  157. if (typeof window !== 'undefined') {
  158. return {
  159. setImmediate: (handler: any, ...args: any[]) => (window as any).setImmediate(handler, ...args as any) as number,
  160. clearImmediate: (handle: any) => (window as any).clearImmediate(handle)
  161. };
  162. } else {
  163. return { setImmediate, clearImmediate };
  164. }
  165. }
  166. return createImmediateActions();
  167. }());
  168. function resolveImmediate(res: () => void) {
  169. immediateActions.setImmediate(res);
  170. }
  171. const Scheduler = {
  172. setImmediate: immediateActions.setImmediate,
  173. clearImmediate: immediateActions.clearImmediate,
  174. immediatePromise() { return new Promise<void>(resolveImmediate); },
  175. delay<T>(timeout: number, value: T | undefined = void 0): Promise<T> { return new Promise(r => setTimeout(r, timeout, value)); }
  176. };
  177. export { Scheduler };