mirror of
https://github.com/Polymer/polymer.git
synced 2025-02-25 18:55:30 -06:00
Merge pull request #4306 from Polymer/2.0-preview-debouncer-classes
Clean up Async and Debouncer interfaces with ES6
This commit is contained in:
commit
af5eae716c
@ -10,106 +10,118 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
<link rel="import" href="boot.html">
|
||||
|
||||
<script>
|
||||
(function(global) {
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
Polymer.Async = {};
|
||||
/** @typedef {{run: function(function(), number=):number, cancel: function(number)}} */
|
||||
let AsyncInterface; // eslint-disable-line no-unused-vars
|
||||
|
||||
/**
|
||||
* 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('');
|
||||
new MutationObserver(() => {
|
||||
this._atEndOfMicrotask();
|
||||
}).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() {
|
||||
const len = this._callbacks.length;
|
||||
for (let i=0; i<len; i++) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
let cb = this._callbacks[i];
|
||||
if (cb) {
|
||||
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;
|
||||
} catch (e) {
|
||||
setTimeout(() => { throw e });
|
||||
}
|
||||
}
|
||||
}
|
||||
this._callbacks.splice(0, len);
|
||||
this._lastVal += len;
|
||||
},
|
||||
|
||||
flush() {
|
||||
this._observer.takeRecords();
|
||||
this._atEndOfMicrotask();
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Object<string, !AsyncInterface>} */
|
||||
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);
|
||||
</script>
|
||||
})();
|
||||
</script>
|
@ -15,30 +15,31 @@ 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; // eslint-disable-line no-unused-vars
|
||||
|
||||
Polymer.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.flush);
|
||||
},
|
||||
|
||||
this._timer = this._asyncModule.run(() => {
|
||||
this._timer = null;
|
||||
this._callback()
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Cancels an active debouncer and returns a reference to itself.
|
||||
*/
|
||||
@ -47,8 +48,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 +57,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 +66,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;
|
||||
})();
|
||||
</script>
|
||||
|
@ -12,6 +12,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script>
|
||||
var capturedErrors = [];
|
||||
var captureEnabled = false;
|
||||
window.addEventListener('error', function(e) {
|
||||
if (captureEnabled) {
|
||||
capturedErrors.push(e);
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="../../../web-component-tester/browser.js"></script>
|
||||
<link rel="import" href="../../src/utils/async.html">
|
||||
</head>
|
||||
@ -72,8 +84,8 @@ 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) {
|
||||
captureEnabled = true;
|
||||
var callCount1 = 0;
|
||||
var callCount2 = 0;
|
||||
var callCount3 = 0;
|
||||
@ -101,112 +113,19 @@ 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() {
|
||||
captureEnabled = false;
|
||||
assert.equal(callCount1, 1);
|
||||
assert.equal(callCount2, 1);
|
||||
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() {
|
||||
|
Loading…
Reference in New Issue
Block a user