a, b, c, d, e Int: a, b, (c, d -> *), (e, d -> *) -> timer  @param  intervalMs  Int The milliseconds between each sent ping.  @param  retryLimit  Int The maximum number of allowed dropped pings. As soon as this limit is surpassed an 'error' will be emitted.  @param  ping  Int, Int -> * A function that will be invoked once upon start and then once at the end of each subsequent interval.  The function receives two arguments, the number of retries left and the total number of retries to attempt. It is invoked for your side-affect and accordingly ping-pong ignores its return value.  @param  onTimeout  Int, Int -> * A function that will be invoked once the maximum allowed drops is surpassed.  The function receives two arguments, the number of successful rounds prior to this failure, and the total number of retries to attempt. It is invoked for your side-affect and accordingly ping-pong ignores its return value.
function PingPong(intervalMs, retryLimit, ping, onTimeout){
  if (typeof intervalMs !== 'number' || intervalMs < 0) throw new Error('intervalMs must be an integer >= 0 but was:' + intervalMs);
  if (typeof retryLimit !== 'number' || retryLimit < 0) throw new Error('retryLimit must be an integer >= 0 but was:' + retryLimit);
  // Create a timer object that will be
  // an event emitter with addition properties
  // for state and configuration settings.
  var timer = {};
  timer.conf = {
    ping: ping,
    onTimeout: onTimeout,
    intervalMs: intervalMs,
    retryLimit: retryLimit
  };
  timer.state = {
    intervalTimer: undefined,
    receivedPong: false,
    retryCounter: Counter(retryLimit),
    roundsCount: 0
  };
  // Its possible that given a very small
  // intervalMs (0 for example) that the
  // return timer will not be setup in time
  // for execution of ping.
  //
  // An example problem is that
  // ping would use the closure to
  // pingPong.clear(timer) only to find that timer
  // is, confusingly, still undefined.
  setImmediate(_start, timer);
  return timer;
}