@@ -303,7 +303,7 @@ export abstract class UpdatingElement extends HTMLElement {
303303 const oldValue = ( this as any ) [ name ] ;
304304 // tslint:disable-next-line:no-any no symbol in index
305305 ( this as any ) [ key ] = value ;
306- this . requestUpdate ( name , oldValue ) ;
306+ this . _requestUpdate ( name , oldValue ) ;
307307 } ,
308308 configurable : true ,
309309 enumerable : true
@@ -442,7 +442,7 @@ export abstract class UpdatingElement extends HTMLElement {
442442 protected initialize ( ) {
443443 this . _saveInstanceProperties ( ) ;
444444 // ensures first update will be caught by an early access of `updateComplete`
445- this . requestUpdate ( ) ;
445+ this . _requestUpdate ( ) ;
446446 }
447447
448448 /**
@@ -565,19 +565,11 @@ export abstract class UpdatingElement extends HTMLElement {
565565 }
566566
567567 /**
568- * Requests an update which is processed asynchronously. This should
569- * be called when an element should update based on some state not triggered
570- * by setting a property. In this case, pass no arguments. It should also be
571- * called when manually implementing a property setter. In this case, pass the
572- * property `name` and `oldValue` to ensure that any configured property
573- * options are honored. Returns the `updateComplete` Promise which is resolved
574- * when the update completes.
575- *
576- * @param name {PropertyKey} (optional) name of requesting property
577- * @param oldValue {any} (optional) old value of requesting property
578- * @returns {Promise } A Promise that is resolved when the update completes.
568+ * This private version of `requestUpdate` does not access or return the
569+ * `updateComplete` promise. This promise can be overridden and is therefore
570+ * not free to access.
579571 */
580- requestUpdate ( name ?: PropertyKey , oldValue ?: unknown ) {
572+ private _requestUpdate ( name ?: PropertyKey , oldValue ?: unknown ) {
581573 let shouldRequestUpdate = true ;
582574 // If we have a property key, perform property update steps.
583575 if ( name !== undefined ) {
@@ -608,6 +600,23 @@ export abstract class UpdatingElement extends HTMLElement {
608600 if ( ! this . _hasRequestedUpdate && shouldRequestUpdate ) {
609601 this . _enqueueUpdate ( ) ;
610602 }
603+ }
604+
605+ /**
606+ * Requests an update which is processed asynchronously. This should
607+ * be called when an element should update based on some state not triggered
608+ * by setting a property. In this case, pass no arguments. It should also be
609+ * called when manually implementing a property setter. In this case, pass the
610+ * property `name` and `oldValue` to ensure that any configured property
611+ * options are honored. Returns the `updateComplete` Promise which is resolved
612+ * when the update completes.
613+ *
614+ * @param name {PropertyKey} (optional) name of requesting property
615+ * @param oldValue {any} (optional) old value of requesting property
616+ * @returns {Promise } A Promise that is resolved when the update completes.
617+ */
618+ requestUpdate ( name ?: PropertyKey , oldValue ?: unknown ) {
619+ this . _requestUpdate ( name , oldValue ) ;
611620 return this . updateComplete ;
612621 }
613622
@@ -617,27 +626,37 @@ export abstract class UpdatingElement extends HTMLElement {
617626 private async _enqueueUpdate ( ) {
618627 // Mark state updating...
619628 this . _updateState = this . _updateState | STATE_UPDATE_REQUESTED ;
620- let resolve : ( r : boolean ) => void ;
629+ let resolve ! : ( r : boolean ) => void ;
630+ let reject ! : ( e : Error ) => void ;
621631 const previousUpdatePromise = this . _updatePromise ;
622- this . _updatePromise = new Promise ( ( res ) => resolve = res ) ;
623- // Ensure any previous update has resolved before updating.
624- // This `await` also ensures that property changes are batched.
625- await previousUpdatePromise ;
632+ this . _updatePromise = new Promise ( ( res , rej ) => {
633+ resolve = res ;
634+ reject = rej ;
635+ } ) ;
636+ try {
637+ // Ensure any previous update has resolved before updating.
638+ // This `await` also ensures that property changes are batched.
639+ await previousUpdatePromise ;
640+ } catch ( e ) {
641+ // Ignore any previous errors. We only care that the previous cycle is
642+ // done. Any error should have been handled in the previous update.
643+ }
626644 // Make sure the element has connected before updating.
627645 if ( ! this . _hasConnected ) {
628646 await new Promise ( ( res ) => this . _hasConnectedResolver = res ) ;
629647 }
630- // Allow `performUpdate` to be asynchronous to enable scheduling of updates.
631- const result = this . performUpdate ( ) ;
632- // Note, this is to avoid delaying an additional microtask unless we need
633- // to.
634- if ( result != null &&
635- typeof ( result as PromiseLike < unknown > ) . then === 'function' ) {
636- await result ;
648+ try {
649+ const result = this . performUpdate ( ) ;
650+ // If `performUpdate` returns a Promise, we await it. This is done to
651+ // enable coordinating updates with a scheduler. Note, the result is
652+ // checked to avoid delaying an additional microtask unless we need to.
653+ if ( result != null ) {
654+ await result ;
655+ }
656+ } catch ( e ) {
657+ reject ( e ) ;
637658 }
638- // TypeScript can't tell that we've initialized resolve.
639- // tslint:disable-next-line:no-unnecessary-type-assertion
640- resolve ! ( ! this . _hasRequestedUpdate ) ;
659+ resolve ( ! this . _hasRequestedUpdate ) ;
641660 }
642661
643662 private get _hasConnected ( ) {
@@ -653,10 +672,13 @@ export abstract class UpdatingElement extends HTMLElement {
653672 }
654673
655674 /**
656- * Performs an element update.
675+ * Performs an element update. Note, if an exception is thrown during the
676+ * update, `firstUpdated` and `updated` will not be called.
657677 *
658- * You can override this method to change the timing of updates. For instance,
659- * to schedule updates to occur just before the next frame:
678+ * You can override this method to change the timing of updates. If this
679+ * method is overridden, `super.performUpdate()` must be called.
680+ *
681+ * For instance, to schedule updates to occur just before the next frame:
660682 *
661683 * ```
662684 * protected async performUpdate(): Promise<unknown> {
@@ -670,17 +692,28 @@ export abstract class UpdatingElement extends HTMLElement {
670692 if ( this . _instanceProperties ) {
671693 this . _applyInstanceProperties ( ) ;
672694 }
673- if ( this . shouldUpdate ( this . _changedProperties ) ) {
674- const changedProperties = this . _changedProperties ;
675- this . update ( changedProperties ) ;
695+ let shouldUpdate = false ;
696+ const changedProperties = this . _changedProperties ;
697+ try {
698+ shouldUpdate = this . shouldUpdate ( changedProperties ) ;
699+ if ( shouldUpdate ) {
700+ this . update ( changedProperties ) ;
701+ }
702+ } catch ( e ) {
703+ // Prevent `firstUpdated` and `updated` from running when there's an
704+ // update exception.
705+ shouldUpdate = false ;
706+ throw e ;
707+ } finally {
708+ // Ensure element can accept additional updates after an exception.
676709 this . _markUpdated ( ) ;
710+ }
711+ if ( shouldUpdate ) {
677712 if ( ! ( this . _updateState & STATE_HAS_UPDATED ) ) {
678713 this . _updateState = this . _updateState | STATE_HAS_UPDATED ;
679714 this . firstUpdated ( changedProperties ) ;
680715 }
681716 this . updated ( changedProperties ) ;
682- } else {
683- this . _markUpdated ( ) ;
684717 }
685718 }
686719
@@ -693,7 +726,8 @@ export abstract class UpdatingElement extends HTMLElement {
693726 * Returns a Promise that resolves when the element has completed updating.
694727 * The Promise value is a boolean that is `true` if the element completed the
695728 * update without triggering another update. The Promise result is `false` if
696- * a property was set inside `updated()`. This getter can be implemented to
729+ * a property was set inside `updated()`. If the Promise is rejected, an
730+ * exception was thrown during the update. This getter can be implemented to
697731 * await additional state. For example, it is sometimes useful to await a
698732 * rendered element before fulfilling this Promise. To do this, first await
699733 * `super.updateComplete` then any subsequent state.
0 commit comments