history-throttled
v1.0.1
Published
A throttled drop-in replacement for history.replaceState and history.pushState.
Downloads
389
Maintainers
Readme
history-throttled
This is a drop-in replacement for
history.replaceState
and
history.pushState
,
with appropriate throttling applied to avoid browser errors.
What's the problem?
If you call history.replaceState
too often, you may get one of the following errors:
- Safari: "SecurityError: Attempt to use history.replaceState() more than 100 times per 30 seconds"
- Chrome: "Throttling navigation to prevent the browser from hanging. See https://crbug.com/1038223. Command line switch --disable-ipc-flooding-protection can be used to bypass the protection"
- Firefox: "Too many calls to Location or History APIs within a short timeframe."
You could catch and ignore these errors, but once browsers hit the rate limit,
they disable all calls to replaceState
for a while.
Features
- Tiny: 0.4 KB min-gzipped with no dependencies
- Smart: prioritizes
pushState
overreplaceState
- Browser-aware: applies different throttling to Safari (310 ms) than other browsers (52 ms)
- Compatible: works in any modern browser, and can be imported from Node
Installation
npm install --save history-throttled
Usage
Replace all your calls to history.pushState
and history.replaceState
and all
assignments to location.hash
as follows:
import { pushState, replaceState } from "history-throttled";
pushState("", "", "/foo"); // instead of history.pushState("", "", "/foo")
replaceState("", "", "/bar"); // instead of history.replaceState("", "", "/bar")
replaceState("", "", "#baz"); // instead of location.hash = "baz"
Even if you only care about replaceState
throttling, you should still replace
all calls to history.pushState
with the throttled version:
- Browsers put both functions on the same timer, so
history.pushState
can fail if you callreplaceState
a lot. - The throttled
pushState
version prevents delayedreplacedState
calls from being executed out-of-order afterpushState
, which would result in a wrong URL state.
Behavior
When you call replaceState
or pushState
more often than every 310
milliseconds (or 52 milliseconds on non-Safari browsers), calls will be
automatically throttled.
pushState
calls will get priority over replaceState
calls. Say you're making
the following calls in quick succession:
pushState("", "", "/a");
pushState("", "", "/b");
pushState("", "", "/c");
replaceState("", "", "/c/1");
replaceState("", "", "/c/2");
This will result in the following behavior:
// Immediately (synchronously):
history.pushState("", "", "/a");
// After 310 milliseconds:
// The most recent pushState call. The intermediate call to "/b"
// is dropped, because it exceeds the allowed rate.
history.pushState("", "", "/c");
// After 620 milliseconds:
// The most recent of any remaining replaceState calls:
history.replaceState("", "", "/c/2");
Node compatibility
The package can safely be imported from Node, for example for server-side
rendering, as long you don't call pushState
or replaceState
.
Testing
To disable all throttling and synchronously pass all calls through to the
history
object, run the following before any calls to pushState
or
replaceState
:
import { setDelay } from "history-throttled";
setDelay(0);
About
Copyright 2023 Jo Liss, licensed under the Apache License, Version 2.0.
Written for use in the calcu.net calculator.