All files / main scheduling-async.ts

100% Statements 28/28
100% Branches 4/4
100% Functions 4/4
100% Lines 28/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 586x 6x 6x                         6x 7x 7x 7x 2x 2x     5x     5x 5x 4x 4x 1x 1x 1x   3x 3x 3x     5x 1x 1x               1x 1x 1x     5x 5x    
import { strict as assert } from 'assert';
import { isSchedulingFailure, Schedule, SchedulingFailure } from './api-types';
import { ComputeScheduleParameters, ComputeScheduleReturnType, workerFactory } from './worker-interface';
 
/**
 * Runs (in a separate thread) the list scheduling algorithm on the given problem instance and returns the result
 * asynchronously.
 *
 * See [the project page](https://github.com/fschopp/project-planning-js) for more information on the algorithm.
 *
 * @param args argument list that will be passed on to {@link computeSchedule}() unaltered
 * @return promise that will be resolved with the solution or rejected with a {@link SchedulingFailure} containing a
 *     human-readable failure description if the problem instance is invalid (for example, has a cyclic dependency
 *     graph)
 */
export function computeScheduleAsync(...args: ComputeScheduleParameters): Promise<Schedule> {
  assert(workerFactory.createWorker !== undefined, 'workerFactory.createWorker cannot be undefined');
  const worker: Worker | SchedulingFailure = workerFactory.createWorker!();
  if (isSchedulingFailure(worker)) {
    const failure: SchedulingFailure = worker;
    return Promise.reject(failure);
  }
 
  let isSettled: boolean = false;
  // From MDN: "the executor is called before the Promise constructor even returns the created object"
  // Hence all worker callbacks are in place before we send it the "go" message below.
  const promise = new Promise<Schedule>((resolve, reject) => {
    worker.onmessage = (event: MessageEvent) => {
      const result: ComputeScheduleReturnType = event.data;
      if (isSchedulingFailure(result)) {
        assert(!isSettled, 'Attempted to settle promise more than once.');
        reject(result);
        isSettled = true;
      } else {
        assert(!isSettled, 'Attempted to settle promise more than once.');
        resolve(result);
        isSettled = true;
      }
    };
    worker.onerror = (event: ErrorEvent) => {
      worker.terminate();
      const failure: SchedulingFailure = 'A runtime error occurred in source file ' +
          `${event.filename} (line ${event.lineno}:${event.colno}):\n${event.message}`;
      // In theory, the worker could still cause an error after it has sent its last message. However, the ES6
      // specification says:
      // "Attempting to resolve or reject a resolved promise has no effect."
      // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects
      // Hence, there is no race condition. On the other hand, we don't want to swallow the problem. Therefore, we
      // add an assertion:
      assert(!isSettled, 'Attempted to settle promise more than once.');
      reject(failure);
      isSettled = true;
    };
  });
  worker.postMessage(args);
  return promise;
}