1

I have inherited an application written using Knockout for the UI and I am curious as to what the effect is of having multiple subscriptions against a single observable property?

I have an observable that has 2 subscriptions. By logging to the console I can see that both subscriptions get fired, one after the other.

Using the following example: (this is very stripped down for brevity in the full code there is lots of logic, some of which is duplicated)

self.VisitDate = ko.observable();

self.VisitDate.subscribe(function (newValue) {
    self.ListItemRemoved(removed);
});   

self.VisitDate.subscribe(function (newValue) {
    self.Basket.VisitDate(newValue);
});

I was thinking that I should see some sort of error, because of the multiple subscriptions, but everything appears to work fine but I cannot find any clear explanation of why this is OK to do?

I am just trying to find out the following:

Is it normal and acceptable to have multiple subscriptions to a single observable? Is there any underlying impact on doing this i.e. race conditions maybe? Is there ever really any need to have multiple subscriptions to achieve something that cannot be achieved in a single subscription?

I appreciate this may be a little shy in detail but I am really just trying to get an understanding of how knockout is doing things under the covers to see whether I should be looking to refactor this code or not.

Reggie
  • 23
  • 4
  • The [observer/observable pattern](https://en.wikipedia.org/wiki/Observer_pattern) is *designed* to allow for multiple subscriptions. The whole idea is to first de-couple a change and the effects of it which then enables you to have *any* amount of effects. KO is nothing strange in this regard. The "race condition" is pretty much a non-issue - JS will run your code in a single thread and an observable will notify each observer one by one anyway. – VLAZ May 29 '19 at 14:49
  • I see so the as they are in 1 thread they will be executed one after the other so the second subscription could act upon changes that have resulted from the execution of the first subscription, have I understood that correctly? – Reggie May 29 '19 at 15:04

1 Answers1

0

The observer/observable design pattern does allow for multiple observers/subscriptions. The purpose of the design pattern in short is:

  1. De-couple the change from the effects of the change.
  2. Allow for any and arbitrary effects to stem from a change.

So, Knockout does that through its observables.

var observable = ko.observable("a");

observable.subscribe(function(newValue) {
  console.log("observer 1", newValue)
});

observable.subscribe(function(newValue) {
  console.log("observer 2", newValue)
});

observable.subscribe(function(newValue) {
  console.log("observer 3", newValue)
});

observable("b");
observable("c");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

There is no race condition to really worry about because your code will be run in a single thread, so it's pretty much guaranteed to be deterministic, unless you explicitly opt-into some sort of multi-threadedness.

An observer will usually also not inherently invoke multiple threads anyway, as the subsribers update functionality is generally going to take the form of something like:

for (subscriber of this.subscribers) {
  subscriber.update(this.value);
}

With this said, you might run into problems but only if subscribers depend on a shared state and you cannot guarantee or know the order each subscription is added. In that case, you can get different results based on the order of the subscriptions. Simple demonstration:

var observable = ko.observable("a");

var sharedState = "";

observable.subscribe(function(newValue) {
  //append to the shared state
  sharedState += newValue;
  console.log("observer 1", sharedState);
});

observable.subscribe(function(newValue) {
  //double up the shared state
  sharedState += sharedState;
  console.log("observer 2", sharedState);
});

observable("b");
observable("c");
observable("d");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

var observable = ko.observable("a");

var sharedState = "";


observable.subscribe(function(newValue) {
  //double up the shared state
  sharedState += sharedState;
  console.log("observer 2", sharedState);
});

observable.subscribe(function(newValue) {
  //append to the shared state
  sharedState += newValue;
  console.log("observer 1", sharedState);
});

observable("b");
observable("c");
observable("d");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

So, this would be a bad practice. It would be best avoided if possible. Unless you can guarantee the order of subscriptions - in the example above, I add the subscriptions one after another which guarantees that they show up in the order they are added. But you might have code that adds subscriptions conditionally or in different parts of the application, in which case it's hard to keep that order under control.

VLAZ
  • 18,437
  • 8
  • 35
  • 54