diff --git a/source/dom/updater.mjs b/source/dom/updater.mjs index 8a5bf59b1351f06ebb0addb5c274cb9dd17ae73f..b769647a5f6607b4b6de5e05039f672bf91d8e09 100644 --- a/source/dom/updater.mjs +++ b/source/dom/updater.mjs @@ -60,6 +60,18 @@ export { Updater, addObjectWithUpdaterToElement }; */ const timerElementEventHandlerSymbol = Symbol("timerElementEventHandler"); +/** + * @private + * @type {symbol} + */ +const pendingDiffsSymbol = Symbol("pendingDiffs"); + +/** + * @private + * @type {symbol} + */ +const processingSymbol = Symbol("processing"); + /** * The updater class connects an object with the DOM. In this way, structures and contents in the DOM can be * programmatically adapted via attributes. @@ -85,6 +97,7 @@ const timerElementEventHandlerSymbol = Symbol("timerElementEventHandler"); * @summary The updater class connects an object with the dom */ class Updater extends Base { + /** * @since 1.8.0 * @param {HTMLElement} element @@ -117,39 +130,50 @@ class Updater extends Base { getCheckStateCallback.call(this), ); - this[internalSymbol].subject.attachObserver( - new Observer(() => { - const s = this[internalSymbol].subject.getRealSubject(); - - const diffResult = diff(this[internalSymbol].last, s); - this[internalSymbol].last = clone(s); - - const promises = []; - - for (const [, change] of Object.entries(diffResult)) { - promises.push( - new Promise((resolve, reject) => { - getWindow().requestAnimationFrame(() => { - try { - removeElement.call(this, change); - insertElement.call(this, change); - updateContent.call(this, change); - updateAttributes.call(this, change); - - resolve(); - } catch (error) { - reject(error); - } - }); - }), - ); - } + this[pendingDiffsSymbol] = []; + this[processingSymbol] = false; - return Promise.all(promises); - }), + this[internalSymbol].subject.attachObserver( + new Observer(() => { + const real = this[internalSymbol].subject.getRealSubject(); + const diffResult = diff(this[internalSymbol].last, real); + this[internalSymbol].last = clone(real); + this[pendingDiffsSymbol].push(diffResult); + return this.#processQueue(); + }), ); } + /** + * @private + * @return {Promise} + */ + async #processQueue() { + if ( this[processingSymbol]) { + return Promise.resolve(); + } + this[processingSymbol] = true; + + while (this[pendingDiffsSymbol].length > 0) { + const diffResult = this[pendingDiffsSymbol].shift(); + for (const [, change] of Object.entries(diffResult)) { + await new Promise((resolve, reject) => { + try { + removeElement.call(this, change); + insertElement.call(this, change); + updateContent.call(this, change); + updateAttributes.call(this, change); + resolve(); + } catch (err) { + reject(err); + } + }); + } + } + + this[processingSymbol] = false; + } + /** * Defaults: 'keyup', 'click', 'change', 'drop', 'touchend' *