@@ -303,7 +303,7 @@ export abstract class UpdatingElement extends HTMLElement {
303
303
const oldValue = ( this as any ) [ name ] ;
304
304
// tslint:disable-next-line:no-any no symbol in index
305
305
( this as any ) [ key ] = value ;
306
- this . requestUpdate ( name , oldValue ) ;
306
+ this . _requestUpdate ( name , oldValue ) ;
307
307
} ,
308
308
configurable : true ,
309
309
enumerable : true
@@ -442,7 +442,7 @@ export abstract class UpdatingElement extends HTMLElement {
442
442
protected initialize ( ) {
443
443
this . _saveInstanceProperties ( ) ;
444
444
// ensures first update will be caught by an early access of `updateComplete`
445
- this . requestUpdate ( ) ;
445
+ this . _requestUpdate ( ) ;
446
446
}
447
447
448
448
/**
@@ -565,19 +565,11 @@ export abstract class UpdatingElement extends HTMLElement {
565
565
}
566
566
567
567
/**
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.
579
571
*/
580
- requestUpdate ( name ?: PropertyKey , oldValue ?: unknown ) {
572
+ private _requestUpdate ( name ?: PropertyKey , oldValue ?: unknown ) {
581
573
let shouldRequestUpdate = true ;
582
574
// If we have a property key, perform property update steps.
583
575
if ( name !== undefined ) {
@@ -608,6 +600,23 @@ export abstract class UpdatingElement extends HTMLElement {
608
600
if ( ! this . _hasRequestedUpdate && shouldRequestUpdate ) {
609
601
this . _enqueueUpdate ( ) ;
610
602
}
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 ) ;
611
620
return this . updateComplete ;
612
621
}
613
622
@@ -617,27 +626,37 @@ export abstract class UpdatingElement extends HTMLElement {
617
626
private async _enqueueUpdate ( ) {
618
627
// Mark state updating...
619
628
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 ;
621
631
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
+ }
626
644
// Make sure the element has connected before updating.
627
645
if ( ! this . _hasConnected ) {
628
646
await new Promise ( ( res ) => this . _hasConnectedResolver = res ) ;
629
647
}
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 ) ;
637
658
}
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 ) ;
641
660
}
642
661
643
662
private get _hasConnected ( ) {
@@ -653,10 +672,13 @@ export abstract class UpdatingElement extends HTMLElement {
653
672
}
654
673
655
674
/**
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.
657
677
*
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:
660
682
*
661
683
* ```
662
684
* protected async performUpdate(): Promise<unknown> {
@@ -670,17 +692,28 @@ export abstract class UpdatingElement extends HTMLElement {
670
692
if ( this . _instanceProperties ) {
671
693
this . _applyInstanceProperties ( ) ;
672
694
}
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.
676
709
this . _markUpdated ( ) ;
710
+ }
711
+ if ( shouldUpdate ) {
677
712
if ( ! ( this . _updateState & STATE_HAS_UPDATED ) ) {
678
713
this . _updateState = this . _updateState | STATE_HAS_UPDATED ;
679
714
this . firstUpdated ( changedProperties ) ;
680
715
}
681
716
this . updated ( changedProperties ) ;
682
- } else {
683
- this . _markUpdated ( ) ;
684
717
}
685
718
}
686
719
@@ -693,7 +726,8 @@ export abstract class UpdatingElement extends HTMLElement {
693
726
* Returns a Promise that resolves when the element has completed updating.
694
727
* The Promise value is a boolean that is `true` if the element completed the
695
728
* 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
697
731
* await additional state. For example, it is sometimes useful to await a
698
732
* rendered element before fulfilling this Promise. To do this, first await
699
733
* `super.updateComplete` then any subsequent state.
0 commit comments