From 5eb6af7d90f9359a7e32fbd80230ef362b01189d Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Thu, 2 Nov 2017 20:41:25 -0700 Subject: [PATCH] refactor(asap): Remove setImmediate polyfill Gets rid of fancy polyfill and uses Promise to schedule instead, since Promise is required for several key parts of the library to work. Also setImmediate does not appear to be getting standardized. BREAKING CHANGE: Old runtimes must polyfill Promise in order to use ASAP scheduling. --- spec/util/Immediate-spec.ts | 728 ++---------------------------------- src/util/Immediate.ts | 249 +----------- 2 files changed, 48 insertions(+), 929 deletions(-) diff --git a/spec/util/Immediate-spec.ts b/spec/util/Immediate-spec.ts index 8c8eaea432..e95200fd04 100644 --- a/spec/util/Immediate-spec.ts +++ b/spec/util/Immediate-spec.ts @@ -1,703 +1,33 @@ import { expect } from 'chai'; -import * as sinon from 'sinon'; -import { ImmediateDefinition } from '../../src/util/Immediate'; -import * as Rx from '../../src/Rx'; - -declare const __root__: any; - -/** @test {ImmediateDefinition} */ -describe('ImmediateDefinition', () => { - let sandbox; - beforeEach(function () { - sandbox = sinon.sandbox.create(); - }); - - afterEach(function () { - sandbox.restore(); - }); - - it('should have setImmediate and clearImmediate methods', () => { - const result = new ImmediateDefinition(__root__); - expect(result.setImmediate).to.be.a('function'); - expect(result.clearImmediate).to.be.a('function'); - }); - - describe('when setImmediate exists on root', () => { - it('should use the setImmediate and clearImmediate methods from root', () => { - let setImmediateCalled = false; - let clearImmediateCalled = false; - - const root = { - setImmediate: () => { - setImmediateCalled = true; - }, - clearImmediate: () => { - clearImmediateCalled = true; - } - }; - - const result = new ImmediateDefinition(root); - - result.setImmediate(() => { - //noop - }); - result.clearImmediate(null); - - expect(setImmediateCalled).to.be.ok; - expect(clearImmediateCalled).to.be.ok; - }); - }); - - describe('prototype.createProcessNextTickSetImmediate()', () => { - it('should create the proper flavor of setImmediate using process.nextTick', () => { - const instance = { - root: { - process: { - nextTick: sinon.spy() - } - }, - runIfPresent: () => { - //noop - }, - partiallyApplied: sinon.spy(), - addFromSetImmediateArguments: sinon.stub().returns(123456) - }; - - const setImmediateImpl = ImmediateDefinition.prototype.createProcessNextTickSetImmediate.call(instance); - - expect(setImmediateImpl).to.be.a('function'); - - const action = () => { - //noop - }; - const handle = setImmediateImpl(action); - - expect(handle).to.equal(123456); - expect(instance.addFromSetImmediateArguments).have.been.called; - expect(instance.partiallyApplied).have.been.calledWith(instance.runIfPresent, handle); - }); - }); - - describe('prototype.createPostMessageSetImmediate()', () => { - it('should create the proper flavor of setImmediate using postMessage', () => { - let addEventListenerCalledWith = null; - - const instance = { - root: { - addEventListener: (name: any, callback: any) => { - addEventListenerCalledWith = [name, callback]; - }, - postMessage: sinon.spy(), - Math: { - random: sinon.stub().returns(42) - } - }, - runIfPresent: sinon.spy(), - addFromSetImmediateArguments: sinon.stub().returns(123456) - }; - - const setImmediateImpl = ImmediateDefinition.prototype.createPostMessageSetImmediate.call(instance); - - expect(setImmediateImpl).to.be.a('function'); - expect(addEventListenerCalledWith[0]).to.equal('message'); - - addEventListenerCalledWith[1]({ data: 'setImmediate$42$123456', source: instance.root }); - - expect(instance.runIfPresent).have.been.calledWith(123456); - - const action = () => { - //noop - }; - const handle = setImmediateImpl(action); - - expect(handle).to.equal(123456); - expect(instance.addFromSetImmediateArguments).have.been.called; - expect(instance.root.postMessage).have.been.calledWith('setImmediate$42$123456', '*'); - }); - }); - - describe('prototype.createMessageChannelSetImmediate', () => { - it('should create the proper flavor of setImmediate that uses message channels', () => { - const port1 = {}; - const port2 = { - postMessage: sinon.spy() - }; - - function MockMessageChannel() { - this.port1 = port1; - this.port2 = port2; - } - - const instance = { - root: { - MessageChannel: MockMessageChannel - }, - runIfPresent: sinon.spy(), - addFromSetImmediateArguments: sinon.stub().returns(123456) - }; - - const setImmediateImpl = ImmediateDefinition.prototype.createMessageChannelSetImmediate.call(instance); - - expect(setImmediateImpl).to.be.a('function'); - expect((port1).onmessage).to.be.a('function'); - - (port1).onmessage({ data: 'something' }); - - expect(instance.runIfPresent).have.been.calledWith('something'); - - const action = () => { - //noop - }; - const handle = setImmediateImpl(action); - - expect(handle).to.equal(123456); - expect(port2.postMessage).have.been.calledWith(123456); - }); - }); - - describe('prototype.createReadyStateChangeSetImmediate', () => { - it('should create the proper flavor of setImmediate that uses readystatechange on a DOM element', () => { - const fakeScriptElement = {}; - - const instance = { - root: { - document: { - createElement: sinon.stub().returns(fakeScriptElement), - documentElement: { - appendChild: sinon.spy(), - removeChild: sinon.spy(), - } - } - }, - runIfPresent: sinon.spy(), - addFromSetImmediateArguments: sinon.stub().returns(123456) - }; - - const setImmediateImpl = ImmediateDefinition.prototype.createReadyStateChangeSetImmediate.call(instance); - - expect(setImmediateImpl).to.be.a('function'); - - const action = () => { - //noop - }; - const handle = setImmediateImpl(action); - - expect(handle).to.equal(123456); - expect(instance.root.document.createElement).have.been.calledWith('script'); - expect((fakeScriptElement).onreadystatechange).to.be.a('function'); - expect(instance.root.document.documentElement.appendChild).have.been.calledWith(fakeScriptElement); - - (fakeScriptElement).onreadystatechange(); - - expect(instance.runIfPresent).have.been.calledWith(handle); - expect((fakeScriptElement).onreadystatechange).to.be.a('null'); - expect(instance.root.document.documentElement.removeChild).have.been.calledWith(fakeScriptElement); - }); - }); - - describe('when setImmediate does *not* exist on root', () => { - describe('when it can use process.nextTick', () => { - it('should use the post message impl', () => { - const nextTickImpl = () => { - //noop - }; - sandbox.stub(ImmediateDefinition.prototype, 'canUseProcessNextTick').returns(true); - sandbox.stub(ImmediateDefinition.prototype, 'canUsePostMessage').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUseMessageChannel').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUseReadyStateChange').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'createProcessNextTickSetImmediate').returns(nextTickImpl); - - const result = new ImmediateDefinition({}); - expect(ImmediateDefinition.prototype.canUseProcessNextTick).have.been.called; - expect(ImmediateDefinition.prototype.canUsePostMessage).not.have.been.called; - expect(ImmediateDefinition.prototype.canUseMessageChannel).not.have.been.called; - expect(ImmediateDefinition.prototype.canUseReadyStateChange).not.have.been.called; - expect(ImmediateDefinition.prototype.createProcessNextTickSetImmediate).have.been.called; - expect(result.setImmediate).to.equal(nextTickImpl); - }); - }); - - describe('when it cannot use process.nextTick', () => { - it('should use the post message impl', () => { - const postMessageImpl = () => { - //noop - }; - sandbox.stub(ImmediateDefinition.prototype, 'canUseProcessNextTick').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUsePostMessage').returns(true); - sandbox.stub(ImmediateDefinition.prototype, 'canUseMessageChannel').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUseReadyStateChange').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'createPostMessageSetImmediate').returns(postMessageImpl); - - const result = new ImmediateDefinition({}); - expect(ImmediateDefinition.prototype.canUseProcessNextTick).have.been.called; - expect(ImmediateDefinition.prototype.canUsePostMessage).have.been.called; - expect(ImmediateDefinition.prototype.canUseMessageChannel).not.have.been.called; - expect(ImmediateDefinition.prototype.canUseReadyStateChange).not.have.been.called; - expect(ImmediateDefinition.prototype.createPostMessageSetImmediate).have.been.called; - expect(result.setImmediate).to.equal(postMessageImpl); - }); - }); - - describe('when it cannot use process.nextTick or postMessage', () => { - it('should use the readystatechange impl', () => { - const messageChannelImpl = () => { - //noop - }; - sandbox.stub(ImmediateDefinition.prototype, 'canUseProcessNextTick').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUsePostMessage').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUseMessageChannel').returns(true); - sandbox.stub(ImmediateDefinition.prototype, 'canUseReadyStateChange').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'createMessageChannelSetImmediate').returns(messageChannelImpl); - - const result = new ImmediateDefinition({}); - expect(ImmediateDefinition.prototype.canUseProcessNextTick).have.been.called; - expect(ImmediateDefinition.prototype.canUsePostMessage).have.been.called; - expect(ImmediateDefinition.prototype.canUseMessageChannel).have.been.called; - expect(ImmediateDefinition.prototype.canUseReadyStateChange).not.have.been.called; - expect(ImmediateDefinition.prototype.createMessageChannelSetImmediate).have.been.called; - expect(result.setImmediate).to.equal(messageChannelImpl); - }); - }); - - describe('when it cannot use process.nextTick, postMessage or Message channels', () => { - it('should use the readystatechange impl', () => { - const readyStateChangeImpl = () => { - //noop - }; - sandbox.stub(ImmediateDefinition.prototype, 'canUseProcessNextTick').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUsePostMessage').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUseMessageChannel').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUseReadyStateChange').returns(true); - sandbox.stub(ImmediateDefinition.prototype, 'createReadyStateChangeSetImmediate').returns(readyStateChangeImpl); - - const result = new ImmediateDefinition({}); - expect(ImmediateDefinition.prototype.canUseProcessNextTick).have.been.called; - expect(ImmediateDefinition.prototype.canUsePostMessage).have.been.called; - expect(ImmediateDefinition.prototype.canUseMessageChannel).have.been.called; - expect(ImmediateDefinition.prototype.canUseReadyStateChange).have.been.called; - expect(ImmediateDefinition.prototype.createReadyStateChangeSetImmediate).have.been.called; - expect(result.setImmediate).to.equal(readyStateChangeImpl); - }); - }); - - describe('when no other methods to implement setImmediate are available', () => { - it('should use the setTimeout impl', () => { - const setTimeoutImpl = () => { - //noop - }; - sandbox.stub(ImmediateDefinition.prototype, 'canUseProcessNextTick').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUsePostMessage').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUseMessageChannel').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUseReadyStateChange').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'createSetTimeoutSetImmediate').returns(setTimeoutImpl); - - const result = new ImmediateDefinition({}); - expect(ImmediateDefinition.prototype.canUseProcessNextTick).have.been.called; - expect(ImmediateDefinition.prototype.canUsePostMessage).have.been.called; - expect(ImmediateDefinition.prototype.canUseMessageChannel).have.been.called; - expect(ImmediateDefinition.prototype.canUseReadyStateChange).have.been.called; - expect(ImmediateDefinition.prototype.createSetTimeoutSetImmediate).have.been.called; - expect(result.setImmediate).to.equal(setTimeoutImpl); - }); - }); - }); - - describe('partiallyApplied', () => { - describe('when passed a function as the first argument', () => { - it('should return a function that takes no arguments and will be called with the passed arguments', () => { - const fn = sinon.spy(); - const result = ImmediateDefinition.prototype.partiallyApplied(fn, 'arg1', 'arg2', 'arg3'); - - expect(result).to.be.a('function'); - expect(fn).not.have.been.called; - - result(); - - expect(fn).have.been.calledWith('arg1', 'arg2', 'arg3'); - }); - }); - - describe('when passed a non-function as an argument', () => { - it('should coerce to a string and convert to a function which will be called by the returned function', () => { - __root__.__wasCalled = null; - const fnStr = '__wasCalled = true;'; - const result = ImmediateDefinition.prototype.partiallyApplied(fnStr); - - expect(result).to.be.a('function'); - - result(); - - expect(__root__.__wasCalled).to.be.true; - - delete __root__.__wasCalled; - }); - }); - }); - - describe('prototype.identify', () => { - it('should use Object.toString to return an identifier string', () => { - function MockObject() { - //noop - } - sandbox.stub(MockObject.prototype, 'toString').returns('[object HEYO!]'); - - const instance = { - root: { - Object: MockObject - } - }; - - const result = (ImmediateDefinition).prototype.identify.call(instance); - - expect(result).to.equal('[object HEYO!]'); - }); - }); - - describe('prototype.canUseProcessNextTick', () => { - describe('when root.process does not identify as [object process]', () => { - it('should return false', () => { - const instance = { - root: { - process: {} - }, - identify: sinon.stub().returns('[object it-is-not-a-tumor]') - }; - - const result = ImmediateDefinition.prototype.canUseProcessNextTick.call(instance); - - expect(result).to.be.false; - expect(instance.identify).have.been.calledWith(instance.root.process); - }); - }); - - describe('when root.process identifies as [object process]', () => { - it('should return true', () => { - const instance = { - root: { - process: {} - }, - identify: sinon.stub().returns('[object process]') - }; - - const result = ImmediateDefinition.prototype.canUseProcessNextTick.call(instance); - - expect(result).to.be.true; - expect(instance.identify).have.been.calledWith(instance.root.process); - }); - }); - }); - - describe('prototype.canUsePostMessage', () => { - describe('when there is a global postMessage function', () => { - describe('and importScripts does NOT exist', () => { - it('should maintain any existing onmessage handler', () => { - const originalOnMessage = () => { - //noop - }; - const instance = { - root: { - onmessage: originalOnMessage - } - }; - - ImmediateDefinition.prototype.canUsePostMessage.call(instance); - expect(instance.root.onmessage).to.equal(originalOnMessage); - }); - - describe('and postMessage is synchronous', () => { - it('should return false', () => { - let postMessageCalled = false; - const instance = { - root: { - postMessage: function () { - postMessageCalled = true; - this.onmessage(); - } - } - }; - - const result = ImmediateDefinition.prototype.canUsePostMessage.call(instance); - expect(result).to.be.false; - expect(postMessageCalled).to.be.true; - }); - }); - - describe('and postMessage is asynchronous', () => { - it('should return true', () => { - let postMessageCalled = false; - const instance = { - root: { - postMessage: function () { - postMessageCalled = true; - const _onmessage = this.onmessage; - setTimeout(() => { _onmessage(); }); - } - } - }; - - const result = ImmediateDefinition.prototype.canUsePostMessage.call(instance); - expect(result).to.be.true; - expect(postMessageCalled).to.be.true; - }); - }); - }); - - describe('and importScripts *does* exist because it is a worker', () => { - it('should return false', () => { - const instance = { - root: { - postMessage: function () { - //noop - }, - importScripts: function () { - //noop - } - } - }; - - const result = ImmediateDefinition.prototype.canUsePostMessage.call(instance); - expect(result).to.be.false; - }); - }); - }); - - describe('when there is NOT a global postMessage function', () => { - it('should return false', () => { - const instance = { - root: {} - }; - - const result = ImmediateDefinition.prototype.canUsePostMessage.call(instance); - - expect(result).to.be.false; - }); - }); - }); - - describe('prototype.canUseMessageChannel', () => { - it('should return true if MessageChannel exists', () => { - const instance = { - root: { - MessageChannel: function () { - //noop - } - } - }; - - const result = ImmediateDefinition.prototype.canUseMessageChannel.call(instance); - - expect(result).to.be.true; - }); - - it('should return false if MessageChannel does NOT exist', () => { - const instance = { - root: {} - }; - - const result = ImmediateDefinition.prototype.canUseMessageChannel.call(instance); - - expect(result).to.be.false; - }); - }); - - describe('prototype.canUseReadyStateChange', () => { - describe('when there is a document in global scope', () => { - it('should return true if created script elements have an onreadystatechange property', () => { - const fakeScriptElement = { - onreadystatechange: null - }; - - const instance = { - root: { - document: { - createElement: sinon.stub().returns(fakeScriptElement) - } - } - }; - - const result = ImmediateDefinition.prototype.canUseReadyStateChange.call(instance); - - expect(result).to.be.true; - expect(instance.root.document.createElement).have.been.calledWith('script'); - }); - - it('should return false if created script elements do NOT have an onreadystatechange property', () => { - const fakeScriptElement = {}; - - const instance = { - root: { - document: { - createElement: sinon.stub().returns(fakeScriptElement) - } - } - }; - - const result = ImmediateDefinition.prototype.canUseReadyStateChange.call(instance); - - expect(result).to.be.false; - expect(instance.root.document.createElement).have.been.calledWith('script'); - }); - }); - - it('should return false if there is no document in global scope', () => { - const instance = { - root: {} - }; - - const result = ImmediateDefinition.prototype.canUseReadyStateChange.call(instance); - - expect(result).to.be.false; - }); - }); - - describe('prototype.addFromSetImmediateArguments', () => { - it('should add to tasksByHandle and increment the nextHandle', () => { - const partiallyAppliedResult = {}; - - const instance = { - tasksByHandle: {}, - nextHandle: 42, - partiallyApplied: sinon.stub().returns(partiallyAppliedResult) - }; - - const args = [() => { - //noop - }, 'foo', 'bar']; - - const handle = ImmediateDefinition.prototype.addFromSetImmediateArguments.call(instance, args); - - expect(handle).to.equal(42); - expect(instance.nextHandle).to.equal(43); - expect(instance.tasksByHandle[42]).to.equal(partiallyAppliedResult); - }); - }); - - describe('clearImmediate', () => { - it('should delete values from tasksByHandle', () => { - const setTimeoutImpl = () => { - //noop - }; - sandbox.stub(ImmediateDefinition.prototype, 'canUseProcessNextTick').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUsePostMessage').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUseMessageChannel').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'canUseReadyStateChange').returns(false); - sandbox.stub(ImmediateDefinition.prototype, 'createSetTimeoutSetImmediate').returns(setTimeoutImpl); - - const Immediate = new ImmediateDefinition({}); - Immediate.tasksByHandle[123456] = () => { - //noop - }; - - expect('123456' in Immediate.tasksByHandle).to.be.true; - - Immediate.clearImmediate(123456); - - expect('123456' in Immediate.tasksByHandle).to.be.false; - }); - }); - - describe('prototype.runIfPresent', () => { - it('should delay running the task if it is currently running a task', () => { - const mockApplied = () => { - //noop - }; - - const instance = { - root: { - setTimeout: sinon.spy(), - Object: Object - }, - currentlyRunningATask: true, - partiallyApplied: sinon.stub().returns(mockApplied) - }; - - ImmediateDefinition.prototype.runIfPresent.call(instance, 123456); - - expect(instance.partiallyApplied).have.been.calledWith((instance).runIfPresent, 123456); - expect(instance.root.setTimeout).have.been.calledWith(mockApplied, 0); - }); - - it('should not error if there is no task currently running and the handle passed is not found', () => { - expect(() => { - const instance = { - root: { - setTimeout: sinon.spy(), - Object: Object - }, - currentlyRunningATask: false, - tasksByHandle: {} - }; - - ImmediateDefinition.prototype.runIfPresent.call(instance, 888888); - }).not.to.throw(); - }); - - describe('when a task is found for the handle', () => { - it('should execute the task and clean up after', () => { - const instance = { - root: { - setTimeout: sinon.spy(), - Object: Object - }, - currentlyRunningATask: false, - tasksByHandle: {}, - clearImmediate: sinon.spy() - }; - - const spy = sinon.stub(); - - spy({ - task: function () { - expect(instance.currentlyRunningATask).to.be.true; - } - }); - instance.tasksByHandle[123456] = spy; - - ImmediateDefinition.prototype.runIfPresent.call(instance, 123456); - expect(instance.clearImmediate).have.been.calledWith(123456); - }); - }); - }); - - describe('prototype.createSetTimeoutSetImmediate', () => { - it('should create a proper setImmediate implementation that uses setTimeout', () => { - const mockApplied = () => { - //noop - }; - - const instance = { - root: { - setTimeout: sinon.spy() - }, - addFromSetImmediateArguments: sinon.stub().returns(123456), - runIfPresent: function () { - //noop - }, - partiallyApplied: sinon.stub().returns(mockApplied) - }; - - const setImmediateImpl = ImmediateDefinition.prototype.createSetTimeoutSetImmediate.call(instance); - - const handle = setImmediateImpl(); - - expect(handle).to.equal(123456); - expect(instance.addFromSetImmediateArguments).have.been.called; - expect(instance.root.setTimeout).have.been.calledWith(mockApplied, 0); - }); - }); - - describe('integration test', () => { - it('should work', (done: MochaDone) => { - const results = []; - Rx.Observable.from([1, 2, 3], Rx.Scheduler.asap) - .subscribe((x: number) => { - results.push(x); - }, () => { - done(new Error('should not be called')); - }, () => { - expect(results).to.deep.equal([1, 2, 3]); - done(); - }); +import { Immediate } from '../../src/util/Immediate'; + +describe('Immediate', () => { + it('should schedule on the next microtask', (done) => { + const results: number[] = []; + results.push(1); + setTimeout(() => results.push(5)); + Immediate.setImmediate(() => results.push(3)); + results.push(2); + Promise.resolve().then(() => results.push(4)); + + setTimeout(() => { + expect(results).to.deep.equal([1, 2, 3, 4, 5]); + done(); + }); + }); + + it('should cancel the task with clearImmediate', (done) => { + const results: number[] = []; + results.push(1); + setTimeout(() => results.push(5)); + const handle = Immediate.setImmediate(() => results.push(3)); + Immediate.clearImmediate(handle); + results.push(2); + Promise.resolve().then(() => results.push(4)); + + setTimeout(() => { + expect(results).to.deep.equal([1, 2, 4, 5]); + done(); }); }); }); diff --git a/src/util/Immediate.ts b/src/util/Immediate.ts index 918a41e298..b285b29ec5 100644 --- a/src/util/Immediate.ts +++ b/src/util/Immediate.ts @@ -2,237 +2,26 @@ Some credit for this helper goes to http://github.com/YuzuJS/setImmediate */ -import { root } from './root'; +let nextHandle = 0; -export class ImmediateDefinition { - setImmediate: (cb: () => void) => number; +const tasksByHandle: { [handle: string]: () => void } = {}; - clearImmediate: (handle: number) => void; - - private identify(o: any): string { - return this.root.Object.prototype.toString.call(o); - } - - tasksByHandle: any; - - nextHandle: number; - - currentlyRunningATask: boolean; - - constructor(private root: any) { - if (root.setImmediate && typeof root.setImmediate === 'function') { - this.setImmediate = root.setImmediate.bind(root); - this.clearImmediate = root.clearImmediate.bind(root); - } else { - this.nextHandle = 1; - this.tasksByHandle = {}; - this.currentlyRunningATask = false; - - // Don't get fooled by e.g. browserify environments. - if (this.canUseProcessNextTick()) { - // For Node.js before 0.9 - this.setImmediate = this.createProcessNextTickSetImmediate(); - } else if (this.canUsePostMessage()) { - // For non-IE10 modern browsers - this.setImmediate = this.createPostMessageSetImmediate(); - } else if (this.canUseMessageChannel()) { - // For web workers, where supported - this.setImmediate = this.createMessageChannelSetImmediate(); - } else if (this.canUseReadyStateChange()) { - // For IE 6–8 - this.setImmediate = this.createReadyStateChangeSetImmediate(); - } else { - // For older browsers - this.setImmediate = this.createSetTimeoutSetImmediate(); - } - - let ci = function clearImmediate(handle: any) { - delete (clearImmediate).instance.tasksByHandle[handle]; - }; - - (ci).instance = this; - - this.clearImmediate = ci; - } - } - - canUseProcessNextTick() { - return this.identify(this.root.process) === '[object process]'; - } - - canUseMessageChannel() { - return Boolean(this.root.MessageChannel); - } - - canUseReadyStateChange() { - const document = this.root.document; - return Boolean(document && 'onreadystatechange' in document.createElement('script')); - } - - canUsePostMessage() { - const root = this.root; - // The test against `importScripts` prevents this implementation from being installed inside a web worker, - // where `root.postMessage` means something completely different and can't be used for this purpose. - if (root.postMessage && !root.importScripts) { - let postMessageIsAsynchronous = true; - let oldOnMessage = root.onmessage; - root.onmessage = function() { - postMessageIsAsynchronous = false; - }; - root.postMessage('', '*'); - root.onmessage = oldOnMessage; - return postMessageIsAsynchronous; - } - - return false; - } - - // This function accepts the same arguments as setImmediate, but - // returns a function that requires no arguments. - partiallyApplied(handler: any, ...args: any[]) { - let fn = function result () { - const { handler, args } = result; - if (typeof handler === 'function') { - handler.apply(undefined, args); - } else { - (new Function('' + handler))(); - } - }; - - (fn).handler = handler; - (fn).args = args; - - return fn; - } - - addFromSetImmediateArguments(args: any[]) { - this.tasksByHandle[this.nextHandle] = this.partiallyApplied.apply(undefined, args); - return this.nextHandle++; - } - - createProcessNextTickSetImmediate() { - let fn = function setImmediate() { - const { instance } = (setImmediate); - let handle = instance.addFromSetImmediateArguments(arguments); - instance.root.process.nextTick(instance.partiallyApplied(instance.runIfPresent, handle)); - return handle; - }; - - (fn).instance = this; - - return fn; - } - - createPostMessageSetImmediate() { - // Installs an event handler on `global` for the `message` event: see - // * https://developer.mozilla.org/en/DOM/window.postMessage - // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages - const root = this.root; - - let messagePrefix = 'setImmediate$' + root.Math.random() + '$'; - let onGlobalMessage = function globalMessageHandler(event: any) { - const instance = (globalMessageHandler).instance; - if (event.source === root && - typeof event.data === 'string' && - event.data.indexOf(messagePrefix) === 0) { - instance.runIfPresent(+event.data.slice(messagePrefix.length)); - } - }; - (onGlobalMessage).instance = this; - - root.addEventListener('message', onGlobalMessage, false); - - let fn = function setImmediate() { - const { messagePrefix, instance } = (setImmediate); - let handle = instance.addFromSetImmediateArguments(arguments); - instance.root.postMessage(messagePrefix + handle, '*'); - return handle; - }; - - (fn).instance = this; - (fn).messagePrefix = messagePrefix; - - return fn; - } - - runIfPresent(handle: any) { - // From the spec: 'Wait until any invocations of this algorithm started before this one have completed.' - // So if we're currently running a task, we'll need to delay this invocation. - if (this.currentlyRunningATask) { - // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a - // 'too much recursion' error. - this.root.setTimeout(this.partiallyApplied(this.runIfPresent, handle), 0); - } else { - let task = this.tasksByHandle[handle]; - if (task) { - this.currentlyRunningATask = true; - try { - task(); - } finally { - this.clearImmediate(handle); - this.currentlyRunningATask = false; - } - } - } - } - - createMessageChannelSetImmediate() { - let channel = new this.root.MessageChannel(); - channel.port1.onmessage = (event: any) => { - let handle = event.data; - this.runIfPresent(handle); - }; - - let fn = function setImmediate() { - const { channel, instance } = (setImmediate); - let handle = instance.addFromSetImmediateArguments(arguments); - channel.port2.postMessage(handle); - return handle; - }; - - (fn).channel = channel; - (fn).instance = this; - - return fn; - } - - createReadyStateChangeSetImmediate() { - let fn = function setImmediate() { - const instance = (setImmediate).instance; - const root = instance.root; - const doc = root.document; - const html = doc.documentElement; - - let handle = instance.addFromSetImmediateArguments(arguments); - // Create a