Best accuracy and performance with timers in JS
BUGs
First some logic and flow bugs.

The function release uses indexOf to get the timer index i then you use the statement if(i) { ... which fails to remove the first item in the instances array. The result is the worker is never shut down.

Calling UberTimer.stop(timer) from within the callback function timer.callback fails to stop the timer, the callback continues to be called. Because you set lastTickTime to -1 your code then tries to catch up by calling the callback at the full rate of the worker's messages.


And related to how you call the callback function.

If the callback function timer.callback fails to return (throws an error) on the function timer.start the timer.lastTickTime property is not set and remains the init value of -1. The callback is then call in rapid succession, and if the callback continues the throw it just continues tring to catch up every time the worker sends a message. The is in  effect an infinite message incomplete loop

Because you call the callback function from the function timer.start any error in the callback will stop current execution context. This is not what one would expect from a timer.

When you have many timers. If any callback throws the remaining callbacks (higher on the instance array) will not get called as the UberTimer.__tick does not get to complete the timer iteration loop.


Danger potencial silent bug.
Because the property UberTimer.worker is exposed there is a danger that you loose the reference to the worker, if it is accidentally or deliberately overwritten. This could result in a silent bug that makes its way to release degrading performance. You should make it safer (protect your state).
Some style notes

Underscore prefixes do not create private / protected variables. If you wish to protect your object's state don't expose functions and properties that you don't want messed with.

C like languages allow the lazy programmers to save milliseconds typing by not including {...} for single line statement blocks. The irony is that the cost is hours of bug hunting. Your eyes don't look for { and } when you think you are looking for a logic bug.
For your coding sanity ALWAYS delimit blocks with {...}


if(foo === blah) 
    foo = 1;
    
 // then later you make the change
 
 
 if(foo === blah)
    foo = 1;
    blah = 1;
    
// Yes obvious in isolation. But in 100's of lines of code with many unblocked statements
// the eye sees the indent and the mind puts in the {}

// never an issue if you always do
if (foo === blah) { foo = 1 }
// or 
if (foo === blah) { 
    foo = 1;
}


Use === or !== never use == or !=

For variables that never change declare them with const

Don't define a variable in one type only to convert it to another when you use it. The worker function can be defined as a constant string.

Don't add redundant or superfluous code it only reduces the quality of the code. module.exports = UberTimer = function UberTimer( reassigning the UberTimer serves no purpose in your code.



 this.blocking = {};
 this.blocking.maxTime = blockThreadMaxMs;
 this.blocking.startTime = -1;


 // should be

 this.blocking = {
     maxTime : blockThreadMaxMs,
     startTime : -1,
 };


Use object function shorthand if you can.


UberTimer.prototype.__makeCall = function() { ... }

UberTimer.prototype.__workerTick = function() { ... }

UberTimer.prototype.start = function() { ... }

UberTimer.prototype.stop = function() { ... }


Is better as
UberTimer.prototype = {
     __makeCall() { ... },
    __workerTick() { ... },
    start() { ... },
    stop() { ... },
};