GithubHelp home page GithubHelp logo

Comments (5)

Westbrook avatar Westbrook commented on May 30, 2024

With multiple and dispose both being optional arguments, I think the protocol itself answers this question, no we don't need them.

Without them, a Provider is free to deliver any of the more complex values that you've defined here. Though it does become the responsibility of the Provider to document those complexities and then Consumer to actively manage those complexities.

With them, even simple data types can be kept up-to-date without the intervention of a second management context. This means that if you choose to use multiple and dispose then the value can always be assumed to be update over time without knowing anything about how to leverage that data.

This flexibility feels like a good balance of capability and API options without being overly strict. In that way, there are even more reactivity models that a Provider can support and they are still able to choose which one they are willing to support.

from community-protocols.

dgp1130 avatar dgp1130 commented on May 30, 2024

With multiple and dispose both being optional arguments, I think the protocol itself answers this question, no we don't need them.

Though it does become the responsibility of the Provider to document those complexities and then Consumer to actively manage those complexities.

I think these are the key points and which directly conflict with each other. dispose() is not optional in the current proposal because any consumer needs to defensively support it just in case the provider requires it.

Of course in an application with two cooperating components maintained in the same codebase with the same ownership, devs can short-cut this process knowing that no, the provider really doesn't require dispose(). But especially in the context of a community protocol, the loose coupling between provider and consumer means that they can't make any assumptions about each other, including whether or not the value should be disposed.

Here's a related question to frame the conversation: Consider a library with a provider component shipped to users and working successfully. This provider does not send a dispose() function because it just isn't necessary. Then one day, I make a change which now requires cleanup, so I add a dispose() function and release a new version. Is this change:

  1. A breaking change because consumers were not expected to implement dispose() previously and now they are.
  2. A non-breaking change because consumers were expected to defensively implement a previously unused dispose(), and since they didn't they have a bad consumer which broke the protocol.

As the protocol is currently written I feel like 2. is at least somewhat implied, but in practice in such a situation I'd probably go with 1. just to avoid angering users. Of course as a provider, I'd probably just not use dispose() and instead use my own reactivity primitive specifically to avoid this. But the question still remains for consumers which don't have luxury.

Every situation is different and we don't necessarily need to decide on a "correct" answer on breaking changed here, but I think it demonstrates the "need" for dispose() as the protocol is currently written and why there might be value in removing it.

from community-protocols.

justinfagnani avatar justinfagnani commented on May 30, 2024

The reason why multiple and dispose() are needed is to reduce memory overhead when a provider can deliver new values. If the consumer is never going to use updated values, then the provider doesn't need to keep a reference to the context-request event's callback. So providers should only store the callback if multiple is true. Then if the consumer no longer needs the values, like when it's disconnected, it should call dispose() to tell the provider to release the reference to the callback.

Doing this releasing easily and ergonomically is critical because if a provider holds on to a callback when it shouldn't, and that callback closure has references into the consumer element (almost all will), then the provider will hold on to consumer elements, which could be a serious memory leak.

The nice thing about this subscribe/dispose protocol is that it's very easily abstracted into helpers that do the right thing on host element disconnection to avoid those leaks. The @lit-labs/context package automatically hooks disconnectedCallback() to call dispose(). I think that kind of DX is very important to ensure correct usage.

The problem with other "subscription" techniques:

  • Promises are async only and one-shot only.
  • Arrays don't notify of new values
  • Generators require a lot of ceremony to get the same behavior, and probably in fact need the same multiple/dispose() API. A provider can't have a single generator per value, because only one consumer would get each value. So it needs to maintain a generator per consumer. But then it needs to know when to release that generator, so it needs a dispose() mechanism.
  • EventTargets are ok, but this protocol already uses Events a lot, they're more heavyweight than function calls, and we need to watch out for performance here. You also would need to call removeEventLister() very similarly to dispose().
  • Subscribables - I don't know what these are. I don't think there's anything by that name in JS or DOM?
  • Signals - same.

from community-protocols.

dgp1130 avatar dgp1130 commented on May 30, 2024

The problem with other "subscription" techniques:

I'm not advocating for any of these as a coon dispose mechanism. Only using them to illustrate the options snd trade offs a provider can choose to use. The protocol itself wouldn't specify any of these, just like it doesn't today.

Subscribables - I don't know what these are. I don't think there's anything by that name in JS or DOM?

I was referring to the interface for consuming Obervables without an RxJS dependency.

https://rxjs.dev/api/index/interface/Subscribable

Signals - same.

https://www.solidjs.com/tutorial/introduction_signals

Again, not saying the protocol should use any of these, just that providers can choose what works for them.

The nice thing about this subscribe/dispose protocol is that it's very easily abstracted into helpers that do the right thing on host element disconnection to avoid those leaks. The @lit-labs/context package automatically hooks disconnectedCallback() to call dispose(). I think that kind of DX is very important to ensure correct usage.

That is a good point. I can see how a standard dispose semantic would be useful for component libraries to implement implicitly so devs don't have to think about it as much. If the dispose semantics are unique to each context and the exact reactivity primitive it choose, then the component library can't do much to abstract away that complexity.

I think for that reason alone I'm convinced that we should have a common dispose mechanism. Apologies for any noise here, it's just helpful to explicitly call out motivations like this sometimes.

Sidebar: Lit's implementation is eerily similar to one I was working on which motivated this issue. 😅

from community-protocols.

benjamind avatar benjamind commented on May 30, 2024

All good points raised, and honestly I went back and forth a number of times on the value of these arguments. In the end it just came down to keeping the protocol as flexible as possible without putting undue burden on implementors. Glad this made sense to more folks than just me!

from community-protocols.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.