ThomasBurleson
11/20/2014 - 2:59 PM

Enhanced version of `docSearcher` webWorker discussed in Pete Darwin's blog [AngularJS Docs Performance](http://www.bacondarwin.co.uk/angula

Enhanced version of docSearcher webWorker discussed in Pete Darwin's blog 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;
    }
  }
}