ko.datasource icon indicating copy to clipboard operation
ko.datasource copied to clipboard

cancelling remote update

Open agrath opened this issue 11 years ago • 4 comments

While not strictly an issue with datasource, from within my source function, I issue an ajax request to the server. This may only take a few moments to complete. However, if a new request is issued (by the parameters changing before the first request comes back) then the results can be applied to the target observable in the wrong order - so you end up with a result that does not match your last update.

The only solution I can think of is to return a handle of some kind from the source function and ensure that only the last issued handle is the one that can update the _target

Thoughts?

agrath avatar Oct 18 '13 03:10 agrath

Here's how I solved this for now, I would rather a generic solution (e.g. in datasource itself.. but because you can't get the callee of the computed write function, there's no way to actually figure out which execution of the source function is currently setting the value (as they may be arriving out of order)

self._getMediaItemDetailSync = 0;
self.getMediaItemDetail = function () {
    var observable = this;
    var id = self.selectedId();
    var trigger = self.forceItemRefresh();
    if (self._getMediaItemDetailSync) {
        self._getMediaItemDetailSync.abort();
        self._getMediaItemDetailSync = 0;
    }
    if (id) {
        var url = remoteDetailUrl;
        self._getMediaItemDetailSync = jQuery.getJSON(url, { id: id },
            function (data) {
                observable(data);
            }
        );
    } else {
        observable(null);
    }
};
self.selected = ko.observable().extend({ datasource: self.getMediaItemDetail });

agrath avatar Oct 18 '13 03:10 agrath

Hi agrath, thank you for your feedback.

This is a tricky issue to resolve as part of the datasource itself. Coordinating requests and responses and then ignoring all responses except for the one originating from the most recent request sounds like the simplest approach from an consumer perspective, but sounds like it could be pretty inefficient. As you've shown, it's more desirable to abort previous requests when making a new request, however, providing an API for that seems like it might be unwieldy.

I'm open to suggestions on ways we could make this scenario a little easier to handle.

CraigCav avatar Oct 21 '13 14:10 CraigCav

Hi Craig

Have been thinking about this.

It's not overly difficult to wire up a computed which is bound to an ajax request so at the end of the day, this is mostly a syntactic advantage and reduces the code you need to write to do this. That in turn makes your viewmodels simpler, especially when dealing with remote data.

I propose the following, focusing on the consumer of course. I am trying to maintain backwards compatibility, if that's not required then we can simply this:

  1. When you pass the function to the datasource extender, it is wrapped in an internal function which is used for tracking state. The wrapper function invokes the generator function and then returns a handle - that can be a sequential id or some other unique item. The datasource extender stores this handle so that when values are updated, it can determine if it was the last invoke or not.
  2. A parameter is passed to the generator function which is a callback that can be used to set the value of the observable (currently this within the generator - and you set it manually with this(result);
  3. We introduce async: [true|false] as a parameter to the datasource extender. In async: false we bind this as we do now, the default, in async: true we pass a callback function to the generator for setting. We could pass the callback either way though.
  4. We curry the callback function with the current synch value from (1) so that when it is invoked, we can ascertain which invoke is calling it
  5. When the callback is invoked, we compare the curried value to the last issued generator call, if it's the most recent and async is true then we set the observable value, otherwise we throw away the result from the generator and don't set the observable
  6. If the callback is invoked and async is false, we just set the observable (this allows you to use a callback regardless as per (3) rather than setting the observable directly via this
  7. Because I think that JSON objects being remotely returned and stored in an observable is probably a common usecase for datasource (at least it is for me), I would also like to see an ajax/json version of datasource. This could be handled as such; if you pass generator as a function, then we assume it is locally generated (or the user is handling the ajax request themselves). If you pass generator as an object, such as the following: { url: '/path/to/remote/handler', params: [function|object], method: ['get'|'post'] } then datasource would invoke an ajax request to url passing params (invoking if required) using method. While we would need to have either an ajax lib shipped or as a dependency or use a microlib inside datasource, such as https://github.com/pyrsmk/qwest (though that lib doesn't seem to support abort, perhaps there is another? .. or we just have jquery as dependency). In this mode, if generator [wrapper] is called while we have an outstanding ajax request, we can abort the outstanding request and issue a new one.
  8. with all of this, it should now be possible to add a debounce or throttle to the generator function, so that overheads of multiple invokes are reduced - i tried doing this originally but I found that it made things not work so good - since throttle meant that the first invoke got called but future invokes within 50ms (e.g. during initialization) didn't get invoked - and that meant that my remote value never got set

Let me know your thoughts?

Gareth

agrath avatar Oct 22 '13 21:10 agrath

added implementation (limited testing) and created pull request. didn't bother with throttle as debounce is implemented when async: true commit notes has sample invokes

https://github.com/CraigCav/ko.datasource/pull/5

agrath avatar Oct 25 '13 04:10 agrath