-
Notifications
You must be signed in to change notification settings - Fork 3k
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
subscribing fails with a function that doesn't have a normal bind()
#6783
Comments
I'm not sure if I understood this correctly. So, this occurs
Am I reading this correct? Given observer's callback requires a function, I would say it implicitly requires given function should behave as |
Yes.
It's a normal function that's fully callable in all ways ( note: arrow functions (which are extremely common in JS) are also not bindable... it's just that What I'm perplexed about is why RxJS needs to be able to But, if Rx really needs to be able to do so for some use-cases, is there an affordance that Rx can offer so users can opt out to avoid the And bonus: if you opt-out and skipped the |
Other core team members might have a different opinion, but in my opinion, this is internal implementation detail of a, or any library that relies on standard behavior of javascript. Yes, as you said It may possible further version of RxJS could remove usage of |
If you like, forget for a moment that it's a "breakage" I'm experiencing. Pretend my request is an additional feature purely from the justification that I want to skip the "unnecessary" (albeit very slim) performance overhead of
Anyone who passes SIDE NOTE: As I was poking around in the code to propose what changes would be necessary, I found this note which seems to indicate that at least part of the reason for In any case, if we think about this issue as a request for a new feature in version 8, I'm imagining it would involve minor changes in two spots, the
I may be missing other details, but I think that seems like it would essentially cover what I'm suggesting. |
Overall, unless I miss some points this request asks to change internals of library, or expose those internals of libaray into public api surface without noticeable gain we can actually achieve. I'd happy to be proven wrong if I could see 1. |
When I posted this issue I didn't know that the
Since it's a real feature, it would be a disruptive breaking change for you to later remove this feature from Rx. Even if you did decide to make such a breaking change, you'd be deprecating it for a release or two and then later actually removing it. In that case, you'd also be deprecating and removing the Even if, hypothetically, you didn't deprecate and gracefully remove the What I'm suggesting is intended to opt-OUT of a feature, not OPT-IN to some other feature. My point: since it's a user-facing "feature", it's not just an internal implementation detail.
I think it's unfortunate that you're unwilling to shift mental contexts. Had I never mentioned anything about breaking functions and was only talking about the performance optimization opt-out, such a hang-up wouldn't be relevant to the discussion.
That's a perfectly good and fair question. I don't have a concrete answer to that, but I can tell you that But it would indeed be interesting to benchmark what sort of performance improvement occurs if the However, absent such research, it seems you're already jumping to the assumption/conclusion -- that it's negligible -- before we've looked into it. Moreover, I think we should think of the performance implications from two angles:
Perhaps it's at least worth discussing if such a minor performance-oriented feature might serve some folks well. |
I think fundamental different point of view is what you consider as user-facing, non implementation detail is in my opinion it is implementation detail that a library can make necessary changes anytime. To say this is user facing, in my opinion I believe there should be feasible user story that this can break their application code when code follows spec. I think I have expressed my own opinion enough and do not think it's ok to repeat mine again. Issue will leave as opened for now in case if any other core member want to share their opinions. |
Whether it's documented or not, the fact that every single subscriber callback is In fact, the code comment I referenced above, which talks about intentionally deprecating the access to
Me too. I think this should be considered as a feature-request. But Rx isn't my library. I don't need to spend any more time advocating for it. If y'all decide to reject the request, c'est la vie. Sorry if I've been disruptive here. |
I see the issue and it's a minor fix. That said, people shouldn't be patching known methods on functions. Haha. |
Apparently, code exists in the wild that will patch function bind to do something other than return a function that will execute the function instance, so we cannot rely on bind. Resolves ReactiveX#6783
I actually have two different PRs for different approaches to this #6796 and #6797. That said, this is a highly unusual ask. Basically, @getify, you're saying "please don't rely on the behavior of I'm not sure what the performance implications are for either of my PRs. I feel like the second one is closest to parity with the original code, however the first one with the arrow functions will be cleaner in the 8.x branch (where we've done away with a deprecated behavior related to the whole bind thing). |
Just to re-hash in the simplest terms. Whatever source$.subscribe({
next(value) {
this.myMethod(value);
},
myMethod(value) {
console.log(value);
}
}) The However, what is being posed as a bug/issue here is something that would break with even the simplest function that relied on function logThisMessage() {
console.log(`Message`, this.message);
}
// This is going to break everything.
logThisMessage.bind = () => { /* lol */ };
function useHiThere(fn) {
return fn.bind({ message: 'hi there' });
}
useHiThere(logThisMessage)(); So I guess what I'm going to ask, @getify, can you present a case why it's reasonable for any library to assume that users have passed in a function with a broken |
That's not what I'm asking. I'm not asking you to change any of your assumptions about how people use your library, nor any default behaviors. Even if I don't use or care about those behaviors, I assume some people do, and that's fine. I am however observing that even your documentation most commonly provides non-this-aware functions (as arrow functions), so I think there's a substantial portion of users, myself included, who are providing your library non-this-aware functions. For those us doing so, I was suggesting/requesting an additional affordance: to opt-out of your library doing the If I provide you a non-this-aware function (like an arrow function), you calling There must be a good chunk of Rx users in one of those two buckets... so on behalf of them, I was asking for the ability to opt-out of that path. How to opt-out is entirely subjective bikeshedding and I don't really much care what such an opt-out would look like.
Speaking of red herrings, I definitely regret mentioning this fact in the subject/OP, as it's not at all the point anymore. As noted several times, now that I understand the situation better, I have shifted my attention away from "it's broken!" to "can I have an opt-out?". Perhaps I should have just closed this thread and opened a new fresh one without the prior baggage. But again, the simple "use case" is: I am providing you a function which does not need or want your |
@benlesh I'm reluctant to indulge your actual question directly, as its focus on my previous-but-now-set-aside motivations may further distract and invite bikeshedding of its own. However, for curiosity/posterity sake, I will explain the kind of functions that led me to even discover that Rx was doing The way I discovered that Rx does this binding of subscribe callbacks is by providing a function to Rx that intentionally has replaced the normal So why did I override the name The special kind of functions I'm talking about are created by my library: Monio. Monio is a monads-in-JS library. As you may be aware, the concept of monads has one central capability associated with it, which often goes by one of several very common names in the FP world: Monio chose, 2+ years ago when I first wrote it, and in an attempt to be as widely approachable as possible, to use all 3 names as aliases, so you can pick whichever your favorite name is for this core monadic behavior. I didn't ever anticipate at the original time of design that the monad instances created by Monio would be anything other than normal objects, so the method names I put on it were never subject to a collision issue. However, I knew that Well, as it so happens, a few months ago, I designed a new monad kind for Monio, and these instances of this monad kind are... functions. Not monads holding a function as a value, but literally the monad instance itself is a function. Now of course, I have a collision issue between monadic
OK, so option 4 is what I chose. It's not perfect, but it's better than (2) and (3) which create an inconsistency. And (1) is a really extreme route that I don't wanna do. Here's where things get more interesting... the new monad kind I've invented for Monio is a special kind of monad that is basically mimicking, or acting as a companion to.... observables. They're called My point? These Writing tests for my library is exactly how I found this current incompatibility. It's obviously quite ironic that in trying to make my library more friendly to Rx users, I discovered this gotcha that makes them ultimately somewhat-less compatible. So then I started wondering if there was some way for Rx to add a small additional opt-out feature, which would smooth over this incompatibility. As I illustrated above, it's not really an option for Monio to rename its methods or create inconsistencies in its design. Ultimately, if this request is denied, it'll just make it a little more error prone for folks to use my library with Rx. That bums me out, but it's not the end of the world. |
I see. Well, for our part, we need to do something to execute user-provided observer handlers for This is actually something that was born out of TC39 discussions on the matter. "Producer interference". Imagine a single branch of a multicast synchronously errors. If it unwinds the stack back to the loop doing to cast, everyone breaks. There's a few solutions out producer interference. Schedule each omission from observable, schedule at the multicast, or handle the error and forward it. The first solution was a no-go as it breaks a lot of important behaviors and makes it impossible to model EventTarget. The second solution still breaks expectations in some cases (I could discuss this more, but I don't want to write a novel here, haha), so the last choice is our only option. All of that said, the use of |
@benlesh thanks for the further explanation. I agree that being able to run user-supplied functions in a If you don't mind a bit further explanation, I'm curious why the |
It's really not required for anything. It's one approach to ensuring we don't break what Anyhow, this all requires careful consideration. I would rather not introduce new features, as we end up supporting them for years after. Ideally any solution would "just work" and be generally beneficial for the community. |
FWIW: This issue has me thinking about a better architecture in general for |
Apparently, code exists in the wild that will patch function bind to do something other than return a function that will execute the function instance, so we cannot rely on bind. Resolves ReactiveX#6783
@getify FYI: the fix for this is published with 7.5.3 |
Also this should work with |
No longer require function binding if we aren't using the deprecated next context. This should improve performance in the common path of consumers subscribing with an object or even with a function. Adds a simple class `ConsumerObserver` which is mostly meant to optimize the number of function refrences created. We should never expose this externally. Related ReactiveX#6783
…to `subscribe`. (#6815) * refactor(SafeSubscriber): optimize perf for ordinary observers No longer require function binding if we aren't using the deprecated next context. This should improve performance in the common path of consumers subscribing with an object or even with a function. Adds a simple class `ConsumerObserver` which is mostly meant to optimize the number of function refrences created. We should never expose this externally. Related #6783 * chore: update comments * refactor(Subscriber): reduce property access
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [rxjs](https://rxjs.dev) ([source](https://github.com/reactivex/rxjs)) | dependencies | patch | [`7.5.2` -> `7.5.4`](https://renovatebot.com/diffs/npm/rxjs/7.5.2/7.5.4) | --- ### Release Notes <details> <summary>reactivex/rxjs</summary> ### [`v7.5.4`](https://github.com/reactivex/rxjs/blob/HEAD/CHANGELOG.md#​754-httpsgithubcomreactivexrxjscompare753754-2022-02-09) [Compare Source](ReactiveX/rxjs@7.5.3...7.5.4) ##### Performance Improvements - removed code that would `bind` functions passed with observers to `subscribe`. ([#​6815](ReactiveX/rxjs#6815)) ([fb375a0](ReactiveX/rxjs@fb375a0)), closes [#​6783](ReactiveX/rxjs#6783) ### [`v7.5.3`](https://github.com/reactivex/rxjs/blob/HEAD/CHANGELOG.md#​753-httpsgithubcomreactivexrxjscompare752753-2022-02-08) [Compare Source](ReactiveX/rxjs@7.5.2...7.5.3) ##### Bug Fixes - **subscribe:** allow interop with Monio and other libraries that patch function bind ([0ab91eb](ReactiveX/rxjs@0ab91eb)), closes [#​6783](ReactiveX/rxjs#6783) </details> --- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Co-authored-by: cabr2-bot <cabr2.help@gmail.com> Co-authored-by: crapStone <crapstone@noreply.codeberg.org> Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1159 Reviewed-by: crapStone <crapstone@noreply.codeberg.org> Co-authored-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org> Co-committed-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [rxjs](https://rxjs.dev) ([source](https://github.com/reactivex/rxjs)) | dependencies | minor | [`7.5.7` -> `7.8.0`](https://renovatebot.com/diffs/npm/rxjs/7.5.7/7.8.0) | --- ### Release Notes <details> <summary>reactivex/rxjs</summary> ### [`v7.8.0`](https://github.com/reactivex/rxjs/blob/HEAD/CHANGELOG.md#​780-httpsgithubcomreactivexrxjscompare770780-2022-12-15) [Compare Source](ReactiveX/rxjs@7.7.0...7.8.0) ##### Features - **buffer:** `closingNotifier` now supports any `ObservableInput` ([#​7073](ReactiveX/rxjs#7073)) ([61b877a](ReactiveX/rxjs@61b877a)) - **delayWhen:** `delayWhen`'s `delayDurationSelector` now supports any `ObservableInput` ([#​7049](ReactiveX/rxjs#7049)) ([dfd95db](ReactiveX/rxjs@dfd95db)) - **sequenceEqual:** `compareTo` now supports any `ObservableInput` ([#​7102](ReactiveX/rxjs#7102)) ([d501961](ReactiveX/rxjs@d501961)) - **share:** `ShareConfig` factory properties now supports any `ObservableInput` ([#​7093](ReactiveX/rxjs#7093)) ([cc3995a](ReactiveX/rxjs@cc3995a)) - **skipUntil:** `notifier` now supports any `ObservableInput` ([#​7091](ReactiveX/rxjs#7091)) ([60d6c40](ReactiveX/rxjs@60d6c40)) - **window:** `windowBoundaries` now supports any `ObservableInput` ([#​7088](ReactiveX/rxjs#7088)) ([8c4347c](ReactiveX/rxjs@8c4347c)) ### [`v7.7.0`](https://github.com/reactivex/rxjs/blob/HEAD/CHANGELOG.md#​770-httpsgithubcomreactivexrxjscompare760770-2022-12-15) [Compare Source](ReactiveX/rxjs@7.6.0...7.7.0) ##### Features - **distinct:** `flush` argument now supports any `ObservableInput` ([#​7081](ReactiveX/rxjs#7081)) ([74c9ebd](ReactiveX/rxjs@74c9ebd)) - **repeatWhen:** `notifier` supports `ObservableInput` ([#​7103](ReactiveX/rxjs#7103)) ([8f1b976](ReactiveX/rxjs@8f1b976)) - **retryWhen:** `notifier` now supports any `ObservableInput` ([#​7105](ReactiveX/rxjs#7105)) ([794f806](ReactiveX/rxjs@794f806)) - **sample:** `notifier` now supports any `ObservableInput` ([#​7104](ReactiveX/rxjs#7104)) ([b18c2eb](ReactiveX/rxjs@b18c2eb)) ### [`v7.6.0`](https://github.com/reactivex/rxjs/blob/HEAD/CHANGELOG.md#​760-httpsgithubcomreactivexrxjscompare757760-2022-12-03) [Compare Source](ReactiveX/rxjs@7.5.7...7.6.0) ##### Bug Fixes - **schedulers:** no longer cause TypeScript build failures when Node types aren't included ([c1a07b7](ReactiveX/rxjs@c1a07b7)) - **types:** Improved subscribe and tap type overloads ([#​6718](ReactiveX/rxjs#6718)) ([af1a9f4](ReactiveX/rxjs@af1a9f4)), closes [#​6717](ReactiveX/rxjs#6717) ##### Features - **onErrorResumeNextWith:** renamed `onErrorResumeNext` and exported from the top level. (`onErrorResumeNext` operator is stil available, but deprecated) ([#​6755](ReactiveX/rxjs#6755)) ([51e3b2c](ReactiveX/rxjs@51e3b2c)) #### [7.5.7](ReactiveX/rxjs@7.5.6...7.5.7) (2022-09-25) ##### Bug Fixes - **schedulers:** improve performance of animationFrameScheduler and asapScheduler ([#​7059](ReactiveX/rxjs#7059)) ([c93aa60](ReactiveX/rxjs@c93aa60)), closes [#​7017](ReactiveX/rxjs#7017), related to [#​7018](ReactiveX/rxjs#7018) and [#​6674](ReactiveX/rxjs#6674) ##### Performance Improvements - **animationFrames:** uses fewer Subscription instances ([#​7060](ReactiveX/rxjs#7060)) ([2d57b38](ReactiveX/rxjs@2d57b38)), closes [#​7018](ReactiveX/rxjs#7018) #### [7.5.6](ReactiveX/rxjs@7.5.5...7.5.6) (2022-07-11) ##### Bug Fixes - **share:** No longer results in a bad-state observable in an edge case where a synchronous source was shared and refCounted, and the result is subscribed to twice in a row synchronously. ([#​7005](ReactiveX/rxjs#7005)) ([5d4c1d9](ReactiveX/rxjs@5d4c1d9)) - **share & connect:** `share` and `connect` no longer bundle scheduling code by default ([#​6873](ReactiveX/rxjs#6873)) ([9948dc2](ReactiveX/rxjs@9948dc2)), closes [#​6872](ReactiveX/rxjs#6872) - **exhaustAll:** Result will now complete properly when flattening all synchronous observables. ([#​6911](ReactiveX/rxjs#6911)) ([3c1c6b8](ReactiveX/rxjs@3c1c6b8)), closes [#​6910](ReactiveX/rxjs#6910) - **TypeScript:** Now compatible with TypeScript 4.6 type checks ([#​6895](ReactiveX/rxjs#6895)) ([fce9aa1](ReactiveX/rxjs@fce9aa1)) #### [7.5.5](ReactiveX/rxjs@7.5.4...7.5.5) (2022-03-08) ##### Bug Fixes - **package:** add types to exports ([#​6802](ReactiveX/rxjs#6802)) ([3750f75](ReactiveX/rxjs@3750f75)) - **package:** add `require` export condition ([#​6821](ReactiveX/rxjs#6821)) ([c8955e4](ReactiveX/rxjs@c8955e4)) - **timeout:** no longer will timeout when receiving the first value synchronously ([#​6865](ReactiveX/rxjs#6865)) ([2330c96](ReactiveX/rxjs@2330c96)), closes [#​6862](ReactiveX/rxjs#6862) ##### Performance Improvements - Don't clone observers unless you have to ([#​6842](ReactiveX/rxjs#6842)) ([3289d20](ReactiveX/rxjs@3289d20)) #### [7.5.4](ReactiveX/rxjs@7.5.3...7.5.4) (2022-02-09) ##### Performance Improvements - removed code that would `bind` functions passed with observers to `subscribe`. ([#​6815](ReactiveX/rxjs#6815)) ([fb375a0](ReactiveX/rxjs@fb375a0)), closes [#​6783](ReactiveX/rxjs#6783) #### [7.5.3](ReactiveX/rxjs@7.5.2...7.5.3) (2022-02-08) ##### Bug Fixes - **subscribe:** allow interop with Monio and other libraries that patch function bind ([0ab91eb](ReactiveX/rxjs@0ab91eb)), closes [#​6783](ReactiveX/rxjs#6783) #### [7.5.2](ReactiveX/rxjs@7.5.1...7.5.2) (2022-01-11) ##### Bug Fixes - operators that ignore input values now use `unknown` rather than `any`, which should resolve issues with eslint no-unsafe-argument ([#​6738](ReactiveX/rxjs#6738)) ([67cb317](ReactiveX/rxjs@67cb317)), closes [#​6536](ReactiveX/rxjs#6536) - **ajax:** crossDomain flag deprecated and properly reported to consumers ([#​6710](ReactiveX/rxjs#6710)) ([7fd0575](ReactiveX/rxjs@7fd0575)), closes [#​6663](ReactiveX/rxjs#6663) #### [7.5.1](ReactiveX/rxjs@7.5.0...7.5.1) (2021-12-28) ##### Bug Fixes - export supporting interfaces from top-level `rxjs` site. ([#​6733](ReactiveX/rxjs#6733)) ([299a1e1](ReactiveX/rxjs@299a1e1)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNC40OC4wIiwidXBkYXRlZEluVmVyIjoiMzQuNzAuNCJ9--> Co-authored-by: cabr2-bot <cabr2.help@gmail.com> Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1668 Reviewed-by: Epsilon_02 <epsilon_02@noreply.codeberg.org> Co-authored-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org> Co-committed-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
[Update: the conversation has shifted down-thread, from this OP being about a "broken" subscriber callback, to whether or not there's any merit to being able to opt-out of
bind(..)
ing, for performance reasons]Describe the bug
I have a unique situation where I'm trying to subscribe to an observable with a function that has an overridden
bind(..)
, which does something different than normalFunction#bind
would do. I tried passing that function as a subscriptionnext
function, andsubscribe(..)
either seems to ignore it (silently) or in some other way just fails to ever call it as expected.Expected behavior
I expected to be able to pass in any function that is normally callable, like
fn(..)
(orfn.call(..)
orfn.apply(..)
) and have it work fine as a subscription.As shown in the reproduction code below, the simple workaround is to wrap my function in an arrow/lambda. That's not awful, but it's annoying and a bit wasteful to have to do so. I'd really like to avoid that.
I wouldn't have expected that subscriber functions must be
this
-bindable. I read through some documentation and I didn't see such a gotcha listed -- apologies if this is documented and I missed it.If it's not documented, I think it probably should be!
My argument against the current behavior is, a lot of people probably pass
=>
functions anyway, so Rx is likely not getting much out of trying to bind thethis
context on those subscription function calls.But if that behavior is just absolutely required by RxJS, I wonder if there could be some way of opting out that doesn't involve having to wrap my function in an arrow/lambda every time? Perhaps, for example, it could be something like one of these:
It could ALSO possibly be that having a path to opt-out of
this
binding -- for those like me don't need or want it -- might even be just ever so slightly faster on execution. ;-)Reproduction code
Reproduction URL
https://stackblitz.com/edit/rxjs-y1wqko
Version
7.5.2
Environment
Node 16
Additional context
The source of these special functions (that don't have a normal behaving
bind(..)
) is from a library I wrote, and I want users to be able to use them with RxJS as easily as possible.The text was updated successfully, but these errors were encountered: