Last active
August 29, 2015 14:10
-
-
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/)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
... I updated the initialize()
function above.
Additionally:
- The
initialize()
supports both try/catch and timeouts for the indexing-phase of the web worker. - The
worker.onmessage
callback is explicitly reset onceindex-ready
is handled; so the web worker is clearly partitioned into two (2) phases: indexing and query/search. - The internal
worker.$queue
FIFO is never exposed/available to external consumers. - The FIFO promise chain guarantees that no query overlaps will occur; a new query will only be posted when the current query is finished.
- You could easily memoize the query results within a internal cache
Looking better and better. Now if it only had some unit tests ....
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
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