From 678074a96d9ac08ef13cd1c60342a567f271ba3d Mon Sep 17 00:00:00 2001 From: Daniel Freedman Date: Fri, 10 Feb 2017 12:59:31 -0800 Subject: [PATCH 1/6] Clean up Async and Debouncer interfaces with ES6 --- src/utils/async.html | 124 +++++++++++++++++++++++----------------- src/utils/debounce.html | 56 +++++++++--------- 2 files changed, 98 insertions(+), 82 deletions(-) diff --git a/src/utils/async.html b/src/utils/async.html index d6273331..50f37c0f 100644 --- a/src/utils/async.html +++ b/src/utils/async.html @@ -14,72 +14,91 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN 'use strict'; - Polymer.Async = {}; + /** @typedef {{run: function(function(), number=):number, cancel: function(number)}} */ + let AsyncInterface; /** * A timer with the async interface. + * @implements {AsyncInterface} */ - Polymer.Async.timeOut = { - after: function(delay) { - return delay === 0 ? Polymer.Async.timeOut : - { - run: function(fn) { - return global.setTimeout(fn, delay); - }, - cancel: global.clearTimeout.bind(global) - } + let timeOut = { + run(fn, delay = 0) { + return window.setTimeout(fn, delay); }, - run: global.setTimeout.bind(global), - cancel: global.clearTimeout.bind(global) - }; - - /** - * requestAnimationFrame with the async interface. - */ - Polymer.Async.animationFrame = { - run: global.requestAnimationFrame.bind(global), - cancel: global.cancelAnimationFrame.bind(global) - }; - - /** - * requestIdleCallback with the async interface. - */ - Polymer.Async.idlePeriod = { - run(fn) { - return global.requestIdleCallback ? global.requestIdleCallback(fn) : global.setTimeout(fn, 16); - }, - cancel(timer) { - return global.cancelIdleCallback ? global.cancelIdleCallback(timer) : global.clearTimeout(timer); + cancel(handle) { + window.clearTimeout(handle); } }; /** - * Micro task with the async interface. + * @param {number} wait + * @return {!AsyncInterface} */ - Polymer.Async.microTask = { - _currVal: 0, - _lastVal: 0, - _callbacks: [], - _twiddleContent: 0, - _twiddle: document.createTextNode(''), + timeOut.after = function(wait) { + let after = { + run(fn) { + return window.setTimeout(fn, wait); + }, + cancel: timeOut.cancel + } + return after; + }; + /** + * requestAnimationFrame with the async interface. + * @implements {AsyncInterface} + */ + let animationFrame = { + run(fn) { + return window.requestAnimationFrame(fn); + }, + cancel(handle) { + return window.cancelAnimationFrame(handle); + } + }; + + /** + * requestIdleCallback with the async interface. + * @implements {AsyncInterface} + */ + let idlePeriod = window.requestIdleCallback ? { + run(fn) { + return window.requestIdleCallback(fn); + }, + cancel(handle) { + return window.cancelIdleCallback(handle); + } + } : timeOut.after(16); + + /** + * Micro task with the async interface. + * @implements {AsyncInterface} + */ + class MicroTask { + constructor() { + this._currVal = 0; + this._lastVal = 0; + this._callbacks = []; + this._twiddleContent = 0; + this._twiddle = document.createTextNode(''); + this.observer = new MutationObserver(() => {this._atEndOfMicrotask()}); + this.observer.observe(this._twiddle, {characterData: true}); + } run(callback) { this._twiddle.textContent = this._twiddleContent++; this._callbacks.push(callback); return this._currVal++; - }, - + } cancel(handle) { const idx = handle - this._lastVal; if (idx >= 0) { if (!this._callbacks[idx]) { - throw 'invalid async handle: ' + handle; + throw `invalid async handle: ${handle}`; } this._callbacks[idx] = null; } - }, - - _atEndOfMicrotask() { + } + _atEndOfMicrotask() { const len = this._callbacks.length; for (let i=0; i} */ + Polymer.Async = { + timeOut, + animationFrame, + idlePeriod, + microTask: new MicroTask() }; - - Polymer.Async.microTask.observer = new window.MutationObserver(function microTaskObserver() { - Polymer.Async.microTask._atEndOfMicrotask(); - }); - Polymer.Async.microTask.observer.observe(Polymer.Async.microTask._twiddle, {characterData: true}); - })(this); diff --git a/src/utils/debounce.html b/src/utils/debounce.html index dec1366a..6c839148 100644 --- a/src/utils/debounce.html +++ b/src/utils/debounce.html @@ -15,30 +15,28 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN (function() { 'use strict'; - /** @constructor */ - Polymer.Debouncer = function Debouncer() { - this._asyncModule = null; - this._callback = null; - this._timer = null; - this.flush = this.flush.bind(this); - }; + /** @typedef {{run: function(function(), number=):number, cancel: function(number)}} */ + let AsyncModule - Polymer.Utils.mixin(Polymer.Debouncer.prototype, { + class Debouncer { + constructor() { + this._asyncModule = null; + this._callback = null; + this._timer = null; + } /** * Sets the scheduler; that is, a module with the Async interface, * a callback and optional arguments to be passed to the run function * from the async module. * - * @param {{run: function, cancel: function}} asyncModule - * @param {function} callback - * @param {Array=} + * @param {!AsyncModule} asyncModule + * @param {function()} callback */ setConfig(asyncModule, cb) { this._asyncModule = asyncModule; this._callback = cb; this._timer = this._asyncModule.run(this._callback); - }, - + } /** * Cancels an active debouncer and returns a reference to itself. */ @@ -47,8 +45,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN this._asyncModule.cancel(this._timer); this._timer = null; } - }, - + } /** * Flushes an active debouncer and returns a reference to itself. */ @@ -57,8 +54,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN this.cancel(); this._callback(); } - }, - + } /** * Returns true if the debouncer is active. * @@ -67,26 +63,26 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN isActive() { return this._timer != null; } - }); - /** * Creates a debouncer if no debouncer is passed as a parameter * or it cancels an active debouncer otherwise. * * @param {Polymer.Debouncer?} debouncer - * @param {{run: function, cancel: function}} asyncModule - * @param {function} cb - * @return {Polymer.Debouncer} + * @param {!AsyncModule} asyncModule + * @param {function()} cb + * @return {!Debouncer} */ - Polymer.Debouncer.debounce = function debounce(debouncer, asyncModule, cb) { - if (debouncer instanceof Polymer.Debouncer) { - debouncer.cancel(); - } else { - debouncer = new Polymer.Debouncer(); + static debounce(debouncer, asyncModule, cb) { + if (debouncer instanceof Debouncer) { + debouncer.cancel(); + } else { + debouncer = new Debouncer(); + } + debouncer.setConfig(asyncModule, cb); + return debouncer; } - debouncer.setConfig(asyncModule, cb); - return debouncer; - }; + } + Polymer.Debouncer = Debouncer; })(); From 670642922e073ae1a58883be6702262b7446997d Mon Sep 17 00:00:00 2001 From: Daniel Freedman Date: Fri, 10 Feb 2017 14:17:34 -0800 Subject: [PATCH 2/6] clean up lint errors --- src/utils/async.html | 6 +++--- src/utils/debounce.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/async.html b/src/utils/async.html index 50f37c0f..4a357a95 100644 --- a/src/utils/async.html +++ b/src/utils/async.html @@ -10,12 +10,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN diff --git a/src/utils/debounce.html b/src/utils/debounce.html index 6c839148..d6988c11 100644 --- a/src/utils/debounce.html +++ b/src/utils/debounce.html @@ -16,7 +16,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN 'use strict'; /** @typedef {{run: function(function(), number=):number, cancel: function(number)}} */ - let AsyncModule + let AsyncModule; // eslint-disable-line no-unused-vars class Debouncer { constructor() { From 911f10cc01e5940ce1e48b21a3fd46935881ffd4 Mon Sep 17 00:00:00 2001 From: Daniel Freedman Date: Fri, 10 Feb 2017 17:08:19 -0800 Subject: [PATCH 3/6] address feedback --- src/utils/async.html | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/utils/async.html b/src/utils/async.html index 4a357a95..8f9b8690 100644 --- a/src/utils/async.html +++ b/src/utils/async.html @@ -106,12 +106,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN try { cb(); } catch(e) { - // Clear queue up to this point & start over after throwing - i++; - this._callbacks.splice(0, i); - this._lastVal += i; - this._twiddle.textContent = this._twiddleContent++; - throw e; + setTimout(() => {throw e}); } } } From 86b06b28e24521bd2277693553ed36488166e44b Mon Sep 17 00:00:00 2001 From: Daniel Freedman Date: Fri, 10 Feb 2017 17:14:12 -0800 Subject: [PATCH 4/6] remove flush --- src/utils/async.html | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/utils/async.html b/src/utils/async.html index 8f9b8690..f36d1063 100644 --- a/src/utils/async.html +++ b/src/utils/async.html @@ -10,7 +10,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN + \ No newline at end of file From 820bb3eaa1b0b247111fff1f2737695f65c7774f Mon Sep 17 00:00:00 2001 From: Daniel Freedman Date: Fri, 24 Feb 2017 12:22:16 -0800 Subject: [PATCH 5/6] Fix tests Remove testing microTask.flush() Capture errors in async test Fix debouncer.flush() when debouncer has completed --- src/utils/async.html | 5 +- src/utils/debounce.html | 5 +- test/unit/async.html | 114 ++++++---------------------------------- 3 files changed, 22 insertions(+), 102 deletions(-) diff --git a/src/utils/async.html b/src/utils/async.html index f36d1063..a605bb0c 100644 --- a/src/utils/async.html +++ b/src/utils/async.html @@ -81,8 +81,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN this._callbacks = []; this._twiddleContent = 0; this._twiddle = document.createTextNode(''); - let observer = new MutationObserver(() => { this._atEndOfMicrotask() }); - observer.observe(this._twiddle, { characterData: true }); + new MutationObserver(() => { + this._atEndOfMicrotask(); + }).observe(this._twiddle, { characterData: true }); } run(callback) { this._twiddle.textContent = this._twiddleContent++; diff --git a/src/utils/debounce.html b/src/utils/debounce.html index 32be38bb..b3fe6d88 100644 --- a/src/utils/debounce.html +++ b/src/utils/debounce.html @@ -35,7 +35,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN setConfig(asyncModule, cb) { this._asyncModule = asyncModule; this._callback = cb; - this._timer = this._asyncModule.run(this._callback); + this._timer = this._asyncModule.run(() => { + this._timer = null; + this._callback() + }); } /** * Cancels an active debouncer and returns a reference to itself. diff --git a/test/unit/async.html b/test/unit/async.html index 54dde6cb..0ac530bf 100644 --- a/test/unit/async.html +++ b/test/unit/async.html @@ -12,6 +12,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN + @@ -72,8 +83,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN }); }); - test('Errors are thrown but the queue is continued in' + - ' another microtask.', function(done) { + test('Errors are thrown but the queue is continued', function(done) { var callCount1 = 0; var callCount2 = 0; var callCount3 = 0; @@ -101,26 +111,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN Polymer.Async.microTask.run(callback2); Polymer.Async.microTask.run(callback3); Polymer.Async.microTask.run(callback4); - // Manually flush the queue so the error can be caught. - assert.throws(function() { - Polymer.Async.microTask.flush(); - }); - // Only `callback1` and `callback2` were called during the last flush. - assert.equal(callCount1, 1); - assert.equal(callCount2, 1); - assert.equal(callCount3, 0); - assert.equal(callCount4, 0); - assert.equal(callCount5, 0); - // Manually flush the queue so the error can be caught. - assert.throws(function() { - Polymer.Async.microTask.flush(); - }); - // Only `callback3` was called during the last flush. - assert.equal(callCount1, 1); - assert.equal(callCount2, 1); - assert.equal(callCount3, 1); - assert.equal(callCount4, 0); - assert.equal(callCount5, 0); // All callbacks have been called by the next task. setTimeout(function() { assert.equal(callCount1, 1); @@ -128,85 +118,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN assert.equal(callCount3, 1); assert.equal(callCount4, 1); assert.equal(callCount5, 1); + assert.equal(capturedErrors.length, 2); + capturedErrors.length = 0; done(); - }); + }, 100); }); - - test('`flush` synchronously runs all functions queued with microtask' + - ' timing.', function() { - var callCount1 = 0; - var callCount2 = 0; - var callCount3 = 0; - var callback1 = function() { - callCount1++; - }; - var callback2 = function() { - callCount2++; - }; - var callback3 = function() { - callCount3++; - }; - Polymer.Async.microTask.run(callback1); - Polymer.Async.microTask.run(callback2); - Polymer.Async.microTask.run(callback3); - Polymer.Async.microTask.flush(); - assert.equal(callCount1, 1); - assert.equal(callCount2, 1); - assert.equal(callCount3, 1); - }); - - test('`flush` rethrows any error thrown by a dequeued callback and leaves' + - ' any remaining callbacks in the queue.', function(done) { - var callCount1 = 0; - var callCount2 = 0; - var callCount3 = 0; - var callback1Error = new Error("callback1Error"); - var callback1 = function() { - callCount1++; - throw callback1Error; - }; - var callback2Error = new Error("callback2Error"); - var callback2 = function() { - callCount2++; - throw callback2Error; - }; - var callback3 = function() { - callCount3++; - }; - Polymer.Async.microTask.run(callback1); - Polymer.Async.microTask.run(callback2); - Polymer.Async.microTask.run(callback3); - try { - Polymer.Async.microTask.flush(); - assert.fail(); - } catch (err) { - assert.equal(err, callback1Error); - } - // `callback1` throws, so items later in the queue were not dequeued or - // called during the flush. - assert.equal(callCount1, 1); - assert.equal(callCount2, 0); - assert.equal(callCount3, 0); - try { - Polymer.Async.microTask.flush(); - assert.fail(); - } catch (err) { - assert.equal(err, callback2Error); - } - // `callback2` throws, so items later in the queue were not dequeued or - // called during the flush. - assert.equal(callCount1, 1); - assert.equal(callCount2, 1); - assert.equal(callCount3, 0); - setTimeout(function() { - // `callback3` was called in another microtask. - assert.equal(callCount1, 1); - assert.equal(callCount2, 1); - assert.equal(callCount3, 1); - done(); - }); - }); - }); suite('Cancelling micro tasks', function() { From cf0d296538e40f6681407c847375f9d2230f19b0 Mon Sep 17 00:00:00 2001 From: Daniel Freedman Date: Fri, 24 Feb 2017 12:44:32 -0800 Subject: [PATCH 6/6] work around safari labeling all errors script error --- test/unit/async.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/unit/async.html b/test/unit/async.html index 0ac530bf..fb75a996 100644 --- a/test/unit/async.html +++ b/test/unit/async.html @@ -14,8 +14,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN