diff --git a/src/dataset.js b/src/dataset.js index eff600a6..56f020a4 100644 --- a/src/dataset.js +++ b/src/dataset.js @@ -248,14 +248,19 @@ var Dataset = (function() { getSuggestions: function(query, cb) { var that = this, terms = utils.tokenizeQuery(query), - suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); + suggestions = this._getLocalSuggestions(terms).slice(0, this.limit), + cacheHit = false; - cb && cb(suggestions); if (suggestions.length < this.limit && this.transport) { - this.transport.get(query, processRemoteData); + cacheHit = this.transport.get(query, processRemoteData); } + // if a cache hit occurred, skip rendering local suggestions + // because the rendering of local/remote suggestions is already + // in the event loop + !cacheHit && cb && cb(suggestions); + // callback for transport.get function processRemoteData(data) { suggestions = suggestions.slice(0); diff --git a/src/transport.js b/src/transport.js index 4beb7255..e29237a2 100644 --- a/src/transport.js +++ b/src/transport.js @@ -34,8 +34,8 @@ var Transport = (function() { beforeSend: o.beforeSend }; - this.get = (/^throttle$/i.test(o.rateLimitFn) ? - utils.throttle : utils.debounce)(this.get, o.rateLimitWait || 300); + this._get = (/^throttle$/i.test(o.rateLimitFn) ? + utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); } utils.mixin(Transport.prototype, { @@ -43,6 +43,32 @@ var Transport = (function() { // private methods // --------------- + _get: function(url, cb) { + var that = this; + + // under the pending request threshold, so fire off a request + if (belowPendingRequestsThreshold()) { + this._sendRequest(url).done(done); + } + + // at the pending request threshold, so hang out in the on deck circle + else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + + // success callback + function done(resp) { + var data = that.filter ? that.filter(resp) : resp; + + cb && cb(data); + + // cache the resp and not the results of applying filter + // in case multiple datasets use the same url and + // have different filters + requestCache.set(url, resp); + } + }, + _sendRequest: function(url) { var that = this, jqXhr = pendingRequests[url]; @@ -60,7 +86,7 @@ var Transport = (function() { // ensures request is always made for the last query if (that.onDeckRequestArgs) { - that.get.apply(that, that.onDeckRequestArgs); + that._get.apply(that, that.onDeckRequestArgs); that.onDeckRequestArgs = null; } } @@ -75,36 +101,24 @@ var Transport = (function() { url, resp; + cb = cb || utils.noop; + url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); // in-memory cache hit if (resp = requestCache.get(url)) { - cb && cb(this.filter ? this.filter(resp) : resp); - } - - // under the pending request threshold, so fire off a request - else if (belowPendingRequestsThreshold()) { - this._sendRequest(url).done(done); + // defer to stay consistent with behavior of ajax call + utils.defer(function() { cb(that.filter ? that.filter(resp) : resp); }); } - // at the pending request threshold, so hang out in the on deck circle else { - this.onDeckRequestArgs = [].slice.call(arguments, 0); + this._get(url, cb); } - // success callback - function done(resp) { - var data = that.filter ? that.filter(resp) : resp; - - cb && cb(data); - - // cache the resp and not the result of applying filter - // in case multiple datasets use the same url and - // have different filters - requestCache.set(url, resp); - } + // return bool indicating whether or not a cache hit occurred + return !!resp; } }); diff --git a/test/dataset_spec.js b/test/dataset_spec.js index e0beb9c3..9ec6e4d1 100644 --- a/test/dataset_spec.js +++ b/test/dataset_spec.js @@ -344,26 +344,30 @@ describe('Dataset', function() { var spy = jasmine.createSpy(), remote = [fixtureDatums[0], fixtureStrings[2]]; - this.dataset.transport.get.andCallFake(function(q, cb) { cb(remote); }); + this.dataset.transport.get.andCallFake(function(q, cb) { + utils.defer(function() { cb(remote); }); + }); this.dataset.getSuggestions('c', spy); - expect(spy.callCount).toBe(2); - - // local suggestions - expect(spy.argsForCall[0][0]).toEqual([ - expectedItemHash.coconut, - expectedItemHash.cake, - expectedItemHash.coffee - ]); - - // local + remote suggestions - expect(spy.argsForCall[1][0]).toEqual([ - expectedItemHash.coconut, - expectedItemHash.cake, - expectedItemHash.coffee, - expectedItemHash.grape - ]); + waitsFor(function() { return spy.callCount === 2; }); + + runs(function() { + // local suggestions + expect(spy.argsForCall[0][0]).toEqual([ + expectedItemHash.coconut, + expectedItemHash.cake, + expectedItemHash.coffee + ]); + + // local + remote suggestions + expect(spy.argsForCall[1][0]).toEqual([ + expectedItemHash.coconut, + expectedItemHash.cake, + expectedItemHash.coffee, + expectedItemHash.grape + ]); + }); }); }); diff --git a/test/transport_spec.js b/test/transport_spec.js index 17c1d253..218e4d7e 100644 --- a/test/transport_spec.js +++ b/test/transport_spec.js @@ -57,11 +57,17 @@ describe('Transport', function() { }); it('should call filter', function() { - expect(this.transport.filter).toHaveBeenCalled(); + waitsFor(function() { + return this.transport.filter.callCount === 1; + }); }); it('should invoke callback with data', function() { - expect(this.spy).toHaveBeenCalledWith(['val']); + waitsFor(function() { return this.spy.callCount === 1; }); + + runs(function() { + expect(this.spy).toHaveBeenCalledWith(['val']); + }); }); }); @@ -120,7 +126,8 @@ describe('Transport', function() { }); it('should set args for the on-deck request', function() { - expect(this.transport.onDeckRequestArgs).toEqual(['bad', $.noop]); + expect(this.transport.onDeckRequestArgs) + .toEqual(['http://example.com?q=bad', $.noop]); }); });