Skip to content

Instantly share code, notes, and snippets.

@ThomasBurleson
Last active August 29, 2015 14:10
Show Gist options
  • 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;
}
}
}
@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