Skip to content

Instantly share code, notes, and snippets.

@ThomasBurleson
Last active August 29, 2015 14:10
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ThomasBurleson/f769907846bd45f52da4 to your computer and use it in GitHub Desktop.
Save ThomasBurleson/f769907846bd45f52da4 to your computer and use it in GitHub Desktop.
Enhanced version of `docSearcher` webWorker discussed in Pete Darwin's blog [AngularJS Docs Performance](http://www.bacondarwin.co.uk/angularjs-docs-performance/)
angular.module('search', [])
.service('docsSearch', ['$q','$rootScope','$timeout','NG_PAGES',BackgroundSearchService]);
/**
* Document search service that uses Web Workers to index
* and search in the background.
*/
function BackgroundSearchService($q, $rootScope, $timeout, NG_PAGES) {
console.log('Using WebWorker Search Index');
// Create persistent instance...
var searcher = new PageSearcher($q, $rootScope, NG_PAGES);
// API that is performant & logical for multiple, sequential queries
return function search(qry) {
return searcher.find(qry);
};
/**
* Create a specialized webWorker to index pages
* and support multiple queries
*
* @returns {{find: function(query) }}
* @constructor
*/
function PageSearcher() {
var worker = initialize('js/search-worker.js');
// Publish simple API
return {
find: processQuery
};
/**
* Add the query to the query queue
* @param qry
* @returns {*}
*/
function processQuery(qry) {
var results = $q.defer();
// Chain each search query...
worker.$queue = worker.$queue
.then(function startQuery() {
return postMessage(qry)
})
.then(function onResponse(pages) {
results.resolve(pages);
});
return results.promise;
}
/**
* Reset the worker.onmessage to send a message when it has
* completed a search query and the results are available
*
* @param qry
* @returns {*}
*/
function postMessage(qry) {
var results = $q.defer();
// Reset the callback...
worker.onmessage = function (oEvent) {
$rootScope.$apply(function () {
switch (oEvent.data.e) {
case 'query-ready':
var pages = oEvent.data.d.map(function (path) {
return NG_PAGES[path];
});
results.resolve(pages);
break;
default :
throw new Error("unknown error");
}
});
};
worker.postMessage({q: qry});
return results.promise;
}
/**
* Initialize a WebWorker and listen for `index-ready` to
* announce ready to run a query.
*
* NOTE: Decorate the worker with a special processing `query` queue
*
* @returns {Worker}
*/
function initialize(path, failAfter) {
var startup = $q.defer();
var timer = $timeout(function() {
startup.reject( "Worker(" + path + ") is not responding..." );
}, failAfter || 5000, false);
try {
var worker = new Worker(path);
worker.$queue = startup.promise;
worker.onmessage = function (oEvent) {
switch (oEvent.data.e) {
case 'index-ready':
$timeout.cancel(timer);
startup.resolve();
break;
}
};
} catch( e ) {
$timeout.cancel(timer);
startup.reject( e.message );
}
return worker;
}
}
}
@petebacondarwin
Copy link

Hi Thomas

Thanks for doing this.

I like where this is going. The idea, I think, is that you are using the chain of promises as a kind of FIFO queue since each time a 'query-ready' message arrives, it resolves the next deferred object in the promise chain.

And since all the promises are guaranteed (I think) to be resolved there is no issue with memory leakage.

I want to think about this a little more before I try implementing it on the docs site.

Pete

@ThomasBurleson
Copy link
Author

... I updated the initialize() function above.

Additionally:

  1. The initialize() supports both try/catch and timeouts for the indexing-phase of the web worker.
  2. The worker.onmessage callback is explicitly reset once index-ready is handled; so the web worker is clearly partitioned into two (2) phases: indexing and query/search.
  3. The internal worker.$queue FIFO is never exposed/available to external consumers.
  4. The FIFO promise chain guarantees that no query overlaps will occur; a new query will only be posted when the current query is finished.
  5. You could easily memoize the query results within a internal cache

@petebacondarwin
Copy link

Looking better and better. Now if it only had some unit tests ....

@ThomasBurleson
Copy link
Author

You sound just like me with the Angular Material team:

  • "Hey Developer, I love those changes... but where are the tests?"
  • "Wow, great fix. Did you write a test for it?"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment