Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle exceptions during update so that they do not break subsequent updates #607

Merged
merged 8 commits into from
Mar 15, 2019

Conversation

sorvell
Copy link
Member

@sorvell sorvell commented Mar 12, 2019

Fixes #262.

Catch exceptions during update and ensure the element marks itself as finished updating.

Note, this change also ensures the updateComplete promise is rejected if there's an exception during update.

…updates

Fixes #262.

Catch exceptions during update and ensure the element marks itself as finished updating.

Note, this change also ensures the `updateComplete` promise is rejected if there's an exception during update.
@sorvell sorvell added this to the 2.0.x milestone Mar 12, 2019
let result = null;
// Trap errors in updating so that element is not in a non-functional state.
try {
// Allow `performUpdate` to be asynchronous to enable scheduling of updates.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't immediately see how the comment relates to the code. What part of the statement allows performUpdate to be async?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

// Note, this is to avoid delaying an additional microtask unless we need
// to.
if (result != null &&
typeof (result as PromiseLike<unknown>).then === 'function') {
await result;
try {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you merge with the previous try/catch block?

try {
  const result = this.performUpdate();
  if (result != null &&
      typeof (result as PromiseLike<unknown>).then === 'function') {
    await result;
  }
} catch (e) {
  this._markUpdated();
  reject(e);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks.

src/lib/updating-element.ts Show resolved Hide resolved
src/lib/updating-element.ts Show resolved Hide resolved
@justinfagnani
Copy link
Contributor

@sorvell should we update any documentation for updateComplete or requestUpdate to mention that they could reject?

@sorvell
Copy link
Member Author

sorvell commented Mar 13, 2019

Added https://github.com/Polymer/lit-element/issues/608 to deal with the documentation.

}
} catch (e) {
// Ensure subsequent updates are ok but reject this update.
this._markUpdated();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_markUpdated is called in performUpdate after update and before firstUpdated; if firstUpdated or updated throws, _markUpdated is called twice. Is that ok? That would potentially throw out property queued changes before the error was thrown. Care?

@sorvell
Copy link
Member Author

sorvell commented Mar 13, 2019

A user error could occur in any of the following overridden spots:

  • performUpdate
    • shouldUpdate
    • update
    • firstUpdated
    • updated

After update, the element should clear the update state. Given this, it makes sense to try/catch shouldUpdate + update to ensure that invariant.

Clearing the update state means the element (a) marks itself as updated, allowing additional updates to be enqueued; and (b) clears any changed properties.

It's important to note that firstUpdated/updated can enqueue an additional update.

If performUpdate throws, it may be before or after a call to super or super may not be called. In that case it's hard to decide what state this should leave the element in. It should definitely do (a) but it's unclear if it should do (b). We could set a didUpdate state in update and only do (b) if that is not set. Or, we could expose the currently private _markUpdated() and require overrides of performUpdate to handle exceptions and ensure this method is called. It's unclear any of this is worth the trouble, however.

@justinfagnani
Copy link
Contributor

Offline review: catching shouldUpdate and update directly in performUpdate is better so we can ensure _markUpdated is called exactly once. Add documentation to performUpdate to say that any override must call super.performUpdate, even in the case of an exception in the override.

Copy link
Contributor

@justinfagnani justinfagnani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flipping approve bit off pending the new updates

* `shouldUpdate`/`update` are try/caught so that the element state is ok for  further updates if an exception is thrown
* `firstUpdated`/`updated` are not called if there's an update exception
* `performUpdate` is try/caught only to reject the update promise. This is done to handle an override producing an exception.
* a private version `_requestUpdate` is called in the property setter to avoid accessing the overridable `updateComplete` promise.
* Added additional js doc comments.
// enable coordinating updates with a scheduler. Note, the result type is
// checked to avoid delaying an additional microtask unless we need to.
if (result != null &&
typeof (result as PromiseLike<unknown>).then === 'function') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code seems superfulous

Copy link
Member

@kevinpschaaf kevinpschaaf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants