Home > Uncategorized > Asynchronous computed observables in Knockout.js

Asynchronous computed observables in Knockout.js

An observable property is a single value that we can get or set, and we can also subscribe to be notified when its value changes. In JavaScript there’s a very nice implementation of an observable property in the lovely Knockout.js library:

var x = ko.observable('My initial value');

console.log(x()); // use x() to read the value

x.subscribe(function() {
    console.log('the value of x just changed to: ' + x());
});

x('A new value'); // use x(v) to assign a value

Atop this simple idea, lots of otherwise hairy interactions (and we all hate those) become surprisingly straightforward.

One tremendous addition is ko.computed. You specify a function that computes a value from other observable properties:

var y = ko.computed(function() {
    return 'How about this? ' + x();
});

Now y is – of course – also an observable, and its value automatically updates whenever anything it depends on (in this case, x) changes. Yes, just like a spreadsheet. While the computation function is running, Knockout is watching to see which other observables are accessed, and automatically subscribes to them on our behalf.

This has a nice implication – we hardly ever need to call subscribe ourselves. We can adjust the first example like this:

ko.computed(function() {
    console.log('the value of x just changed to: ' + x());
});

No need to manually figure out what observables our code depends on! Just wrap it in ko.computed, and it will automatically re-execute whenever there is a change in any of the observables it consumes. (Hint: our computation function returns nothing, which means really it returns undefined, and so the observable returned from ko.computed will always have that value, and so we don’t need to store it in a variable – we just want the auto-subscribe behaviour).

Then there’s another brilliant idea, described here – actually it’s two or three brilliant ideas.

The first brilliant idea is that you can say .extend( ...blah...) after you define a ko.computed, to enhance how it works. And we can create our own extensions this way, by adding them to the ko.extenders object.

The second brilliant idea is that we can keep a bit of data in an observable, and then use a ko.computed to start an AJAX request to retrieve data from the server, based on what is in the observable. Every time the local data changes, another request happens. And of course the resulting information is stuffed into another observable, to which the UI is bound, so the screen updates. It’s such a neat way to do it.

The third brilliant idea is to combine the above two ideas, and make an extender that automatically delays the execution of a computation function until a specific time interval (in milliseconds) after the required observables have stopped changing. This will stop too many AJAX requests being fired off while the user is still fiddling with values. When they leave the keyboard alone for half a second, then we actually act on the new information. Or as the example puts it:

this.throttledValue = ko.computed(this.instantaneousValue)
                        .extend({ throttle: 400 });

Now, there is actually a problem with the second idea, which is that it is possible that if you hit the backend with two subsequent AJAX requests, the first one might take longer to return than the second, and so arrive last, and so the screen will be left in an inconsistent state: old data from the server, despite a more recent local state.

This is addressed by an example on the Knockout wiki, which is well worth reading to the end (note that “dependent observable” is just the longer/older name for ko.computed).

On the downside, that part at the end hasn’t yet made it into an extender. But we can fix that!

ko.extenders.async = function(computedDeferred, initialValue) {

    var plainObservable = ko.observable(initialValue), currentDeferred;
    plainObservable.inProgress = ko.observable(false);

    ko.computed(function() {
        if (currentDeferred) {
            currentDeferred.reject();
            currentDeferred = null;
        }

        var newDeferred = computedDeferred();
        if (newDeferred &&
            (typeof newDeferred.done == "function")) {

            // It's a deferred
            plainObservable.inProgress(true);

            // Create our own wrapper so we can reject
            currentDeferred = $.Deferred().done(function(data) {
                plainObservable.inProgress(false);
                plainObservable(data);
            });
            newDeferred.done(currentDeferred.resolve);
        } else {
            // A real value, so just publish it immediately
            plainObservable(newDeferred);
        }
    });

    return plainObservable;
};

How to use it? Here’s a real example from my code:

var displayName = ko.computed(function() {

    return internalName() ?
        backend.getDisplayName(internalName()) :
        localStrings.none;

}).extend({ async: localStrings.none }),

Here, internalName is an ordinary observable containing something like a GUID. The backend.getDisplayName function asks the server for a human-readable name, so of course its asynchronous, and it returns a $.Deferred. But if we don’t currently know an internalName, I just return localStrings.none, which is an ordinary string.

Then we add the .extend({ async: localStrings.none }) part, so what displayName actually contains is the plainObservable built by the extender. So now I can include displayName in my view model, and bind it to a <span> in my UI. When internalName changes, the UI updates soon after.

The value passed for the async property is the initial value that the observable will have when there is not yet a response from the server. You can make it anything that your view model will be able to cope with.

And of course, you can chain extenders, so you could put:

extend({ throttle: 300 }).extend({ async: localStrings.none })

As always, it’s when the real life examples get more complex, and you have layers of things that depend on other things, that this becomes a really valuable way to cut through the complexity and get you back to a simple declaration of how all the data is related.

Advertisements
  1. Dresel
    February 1, 2013 at 3:03 pm

    I like this and modified it slightly for using it with the mapping plugin. What do you think about that:

    ko.extenders.asyncMap = function(computedDeferred, options) {

    var viewModel = options.viewModel;
    var mapping = (!!options.mapping) ? options.mapping : [];

    var currentDeferred;
    viewModel.InProgress = ko.observable(false);

    ko.computed(function() {
    if (currentDeferred) {
    currentDeferred.reject();
    currentDeferred = null;
    }

    var newDeferred = computedDeferred();
    if (newDeferred &&
    (typeof newDeferred.done == “function”)) {

    // It’s a deferred
    viewModel.InProgress(true);

    // Create our own wrapper so we can reject
    currentDeferred = $.Deferred().done(function(data) {
    viewModel.InProgress(false);

    ko.mapping.fromJS(data, mapping, viewModel);
    });

    newDeferred.done(currentDeferred.resolve);
    } else {
    // A real value, so just publish it immediately
    ko.mapping.fromJS(newDeferred, mapping, viewModel);
    }
    });

    return viewModel;
    };

    Thats how i called it inside my ViewModel:

    self.QueryResultViewModel = ko.computed(function () {
    return $.getJSON(“/Home/Data”, { pageIndex: self.PageIndex, pageSize: self.PageSize });
    }).extend({ asyncMap: { viewModel: new QueryResultViewModel(), mapping: mapping } });

    • February 9, 2014 at 9:19 pm

      Could you maybe give me a complete example of JSFiddle? I can’t get your code to work.
      Thank you.

      • Dresel
        July 25, 2014 at 3:47 pm

        A little bit late, but it might help someone: http://jsfiddle.net/c5G3w/

      • Dresel
        July 25, 2014 at 4:02 pm

        Above doesn’t work for IE / Chrome (github reference): jsfiddle.net/c5G3w/1/

  2. February 13, 2013 at 12:16 pm

    Hi Daniel, great post thanks.

    I’m having trouble chaining the async observable to another computed observable. Using the logChange extender I can see that my async observable is changing ok and when tied to the UI I can see the UI being updated. However when another computed observable tries to use the value, using the debugger I can see that the 2nd computed observable isn’t being called. I have experimented with subscribing but no luck so far… any tips?

    It would be really useful to have your example in a jsfiddle if you have it, to be able to see the full code in action?

    thanks for your help
    Alex.

  3. February 13, 2013 at 3:08 pm

    Actually don’t worry, my bad. I managed to get it working fine, it was another problem entirely doh!

  4. October 31, 2013 at 8:50 pm

    hello – how would one do this with Q?

    • earwicker
      October 31, 2013 at 11:46 pm

      You’d be pretty much starting from scratch! Q is a different pattern entirely. A promise can only transition once, from having no value to have a value. After that it is stuck with the same value forever, by design. Subscribers who subscribe before the transition will get called when the transition occurs. Any who subscribe after the transition will immediately be called with the value. That’s it.

      A Knockout observable is a very different thing: it can change value any number of times. If you subscribe later, you don’t get called about any earlier changes (also there’s the automatic dependency tracking, which is the really good thing about knockout, and a pure promise library like Q has nothing like that in it).

  5. Diego
    February 26, 2015 at 4:12 pm

    This is what I got for Q promise, it seems to work but haven’t fully tested.:

    ko.extenders.async = function(computedDeferred, initialValue) {

    var plainObservable = ko.observable(initialValue), currentDeferred;
    plainObservable.inProgress = ko.observable(false);

    ko.computed(function() {
    if (currentDeferred) {
    currentDeferred = Q(currentDeferred);
    currentDeferred.reject();
    currentDeferred = null;
    }

    var newDeferred = computedDeferred();
    if (newDeferred &&
    (typeof newDeferred.done == “function”)) {

    // It’s a deferred
    plainObservable.inProgress(true);

    // Create our own wrapper so we can reject
    currentDeferred = Q.defer();
    currentDeferred.promise.done(function(data) {
    plainObservable.inProgress(false);
    plainObservable(data);
    return Q.resolve(data);
    });
    newDeferred.done(currentDeferred.resolve);
    } else {
    // A real value, so just publish it immediately
    plainObservable(newDeferred);
    }
    });

    return plainObservable;
    };

    • earwicker
      February 26, 2015 at 5:11 pm

      Ah, I see what you meant… You should check out my new post on this subject [link], as you may get problems due to that orphan ko.computed.

      • Diego
        February 26, 2015 at 8:49 pm

        Thanks! very informative posting.

  6. Tom Pereira
    August 18, 2015 at 10:22 am

    FYI – I modified this slightly to expose the dispose of the wrapped computed on the plainObservable, since we aim to dispose all computeds in our application to avoid memory leaks.

    i.e.

    var computed = ko.computed(function () {

    […]

    plainObservable.dispose = computed.dispose;

  7. Frans Trentelman
    June 22, 2016 at 11:38 am

    Hi Daniel,
    I’m quite new to knockout.
    I’ ve implemented your solution , and changed only one line to make it work for an observableArray. this one:

    var plainObservable = ko.observableArray(initialValue), currentDeferred;

    My aim is to fill an observableArray from json coming from the webserver.
    The thing is: I’ve got it working up to a point. I’m doing:

    self.pitches = ko.computed(function () {
    return $.getJSON(“/api/Pitch”, function (data) {
    var obs = ko.mapping.fromJS([])
    ko.mapping.fromJS(data, {}, obs) ;
    // seems to work, but somehow observables are changed back into objects
    return obs;
    })
    }, this).extend({ asyncArray: [{ Id: 1, PitchNumber: 1, Length: 0, Width: 0, HasElectricity: false, Name: “Test” }] });

    When I put a breakpoint on return obs; obs looks like an observable array, and also the objects (named pitches) have observable properties.

    However, after binding to the view, I see that the objects no longer have observable properties!
    Am I missing something?
    Does the computed function has something to do with it?

    Hope you have an anwer for me.

    regards,
    Frans

    • Mark Shehan
      January 10, 2017 at 10:19 pm

      Frans, sorry this is a late reply but I had a similar issue today and came across this on google. I fixed it for an array as I had the same issue.

      The first problem is that the .done function you wrote is ignored as the code hooks into the deferred function and attaches its own “done” listener which updates the properties.

      So the trick is to pass in your “done” function to the extender. I used an options parameter to enhance it later.

      This is my asyncarray extender…

      ko.extenders.asyncarray = function (computedDeferred, options) {
      var observableArray = ko.observableArray(options.initialValue).extend({ deferred: true }), currentDeferred;
      observableArray.inProgress = ko.observable(false);
      ko.computed(function () {
      if (currentDeferred) {
      currentDeferred.reject();
      currentDeferred = null;
      }
      var newDeferred = computedDeferred();
      if (newDeferred &&
      (typeof newDeferred.done == “function”)) {
      // It’s a deferred
      observableArray.inProgress(true);
      // Create our own wrapper so we can reject
      currentDeferred = $.Deferred().done(function (data) {
      observableArray.removeAll();
      observableArray.inProgress(false);
      $.each(options.doneFunction(data), function (ix, item) {
      observableArray.push(item);
      });
      });
      newDeferred.done(currentDeferred.resolve);
      }
      else {
      // A real value, so just publish it immediately
      $.each(options.doneFunction(newDeferred), function (ix, item) {
      observableArray.push(item);
      });
      }
      });
      return observableArray;
      };

      and your calling code would be

      var doneFunction = { function (data) {
      var obs = ko.mapping.fromJS([])
      ko.mapping.fromJS(data, {}, obs) ;
      return obs;
      }

      var initialValue = [{ Id: 1, PitchNumber: 1, Length: 0, Width: 0, HasElectricity: false, Name: “Test” }];

      self.pitches = ko.computed(function () {
      return $.getJSON(“/api/Pitch”);
      }, this).extend({ asyncArray: {initialValue: initialValue, doneFunction: doneFunction } });

      Hope that helps someone looking to do it with observable arrays. Just past your “done function” that you normally write in the ajax call as the options parameter instead.

  1. August 4, 2014 at 2:07 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: