Skip to content

Commit 32d6951

Browse files
jquensetaion
andauthored
feat(useTimeout): handle large delays (#21)
* feat(useTimeout): handle large delays * adjust for drift * fix Co-authored-by: Jimmy Jia <[email protected]>
1 parent 888e74e commit 32d6951

File tree

3 files changed

+123
-50
lines changed

3 files changed

+123
-50
lines changed

src/useTimeout.ts

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,61 @@
1-
import { useRef } from 'react'
2-
import useWillUnmount from './useWillUnmount'
1+
import { useMemo, useRef } from 'react'
32
import useMounted from './useMounted'
3+
import useWillUnmount from './useWillUnmount'
4+
5+
/*
6+
* Browsers including Internet Explorer, Chrome, Safari, and Firefox store the
7+
* delay as a 32-bit signed integer internally. This causes an integer overflow
8+
* when using delays larger than 2,147,483,647 ms (about 24.8 days),
9+
* resulting in the timeout being executed immediately.
10+
*
11+
* via: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
12+
*/
13+
const MAX_DELAY_MS = 2 ** 31 - 1
14+
15+
function setChainedTimeout(handleRef, fn, timeoutAtMs) {
16+
const delayMs = timeoutAtMs - Date.now()
17+
18+
handleRef.current =
19+
delayMs <= MAX_DELAY_MS
20+
? setTimeout(fn, delayMs)
21+
: setTimeout(
22+
() => setChainedTimeout(handleRef, fn, timeoutAtMs),
23+
MAX_DELAY_MS,
24+
)
25+
}
426

527
/**
628
* Returns a controller object for setting a timeout that is properly cleaned up
729
* once the component unmounts. New timeouts cancel and replace existing ones.
830
*/
931
export default function useTimeout() {
1032
const isMounted = useMounted()
11-
const handle = useRef<number | undefined>()
1233

13-
const clear = () => clearTimeout(handle.current)
34+
// types are confused between node and web here IDK
35+
const handleRef = useRef<any>()
36+
37+
useWillUnmount(() => clearTimeout(handleRef.current))
1438

15-
useWillUnmount(clear)
39+
return useMemo(() => {
40+
const clear = () => clearTimeout(handleRef.current)
1641

17-
return {
18-
set(fn: () => void, ms?: number) {
42+
function set(fn: () => void, delayMs = 0): void {
1943
if (!isMounted()) return
2044

2145
clear()
22-
handle.current = setTimeout(fn, ms)
23-
},
24-
clear,
25-
}
46+
47+
if (delayMs <= MAX_DELAY_MS) {
48+
// For simplicity, if the timeout is short, just set a normal timeout.
49+
handleRef.current = setTimeout(fn, delayMs)
50+
} else {
51+
setChainedTimeout(handleRef, fn, Date.now() + delayMs)
52+
}
53+
54+
}
55+
56+
return {
57+
set,
58+
clear,
59+
}
60+
}, [])
2661
}

test/useTimeout.test.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import React, { useEffect } from 'react'
2-
31
import { mount } from 'enzyme'
4-
2+
import React, { useEffect } from 'react'
53
import useTimeout from '../src/useTimeout'
64

75
describe('useTimeout', () => {
@@ -17,7 +15,7 @@ describe('useTimeout', () => {
1715
return <span />
1816
}
1917

20-
const wrapper = mount(<Wrapper />)
18+
mount(<Wrapper />)
2119

2220
timeout!.set(spy, 100)
2321

@@ -73,4 +71,32 @@ describe('useTimeout', () => {
7371

7472
expect(spy).toHaveBeenCalledTimes(0)
7573
})
74+
75+
it('should handle very large timeouts', () => {
76+
jest.useFakeTimers()
77+
78+
let spy = jest.fn()
79+
let timeout: ReturnType<typeof useTimeout>
80+
81+
function Wrapper() {
82+
timeout = useTimeout()
83+
84+
return <span />
85+
}
86+
87+
mount(<Wrapper />)
88+
89+
const MAX = 2 ** 31 - 1
90+
91+
timeout!.set(spy, MAX + 100)
92+
93+
// some time to check that it didn't overflow and fire immediately
94+
jest.runTimersToTime(100)
95+
96+
expect(spy).toHaveBeenCalledTimes(0)
97+
98+
jest.runAllTimers()
99+
100+
expect(spy).toHaveBeenCalledTimes(1)
101+
})
76102
})

yarn.lock

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,12 @@
438438
esutils "^2.0.2"
439439
js-tokens "^4.0.0"
440440

441-
"@babel/[email protected]", "@babel/parser@^7.0.0-beta.54", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.7.2":
441+
442+
version "7.8.3"
443+
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.3.tgz#790874091d2001c9be6ec426c2eed47bc7679081"
444+
integrity sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==
445+
446+
"@babel/parser@^7.0.0-beta.54", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.7.2":
442447
version "7.7.3"
443448
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.3.tgz#5fad457c2529de476a248f75b0f090b3060af043"
444449
integrity sha512-bqv+iCo9i+uLVbI0ILzKkvMorqxouI+GbV13ivcARXn9NNEabi2IEz912IgNpT/60BNXac5dgcfjb94NjsF33A==
@@ -1432,10 +1437,10 @@
14321437
dependencies:
14331438
"@types/yargs-parser" "*"
14341439

1435-
"@typescript-eslint/typescript-estree@2.8.0":
1436-
version "2.8.0"
1437-
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.8.0.tgz#fcc3fe6532840085d29b75432c8a59895876aeca"
1438-
integrity sha512-ksvjBDTdbAQ04cR5JyFSDX113k66FxH1tAXmi+dj6hufsl/G0eMc/f1GgLjEVPkYClDbRKv+rnBFuE5EusomUw==
1440+
"@typescript-eslint/typescript-estree@2.11.0":
1441+
version "2.11.0"
1442+
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.11.0.tgz#21ada6504274cd1644855926312c798fc697e9fb"
1443+
integrity sha512-HGY4+d4MagO6cKMcKfIKaTMxcAv7dEVnji2Zi+vi5VV8uWAM631KjAB5GxFcexMYrwKT0EekRiiGK1/Sd7VFGA==
14391444
dependencies:
14401445
debug "^4.1.1"
14411446
eslint-visitor-keys "^1.1.0"
@@ -3095,10 +3100,10 @@ diff-sequences@^24.9.0:
30953100
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
30963101
integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
30973102

3098-
3099-
version "4.0.1"
3100-
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
3101-
integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
3103+
3104+
version "4.0.2"
3105+
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
3106+
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
31023107

31033108
31043109
version "2.0.0"
@@ -3956,10 +3961,10 @@ flatten@^1.0.2:
39563961
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
39573962
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
39583963

3959-
flow-parser@0.112.0:
3960-
version "0.112.0"
3961-
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.112.0.tgz#938d7949068f147a196ebc2bd982ea066b024df5"
3962-
integrity sha512-sxjnwhR76B/fUN6n/XerYzn8R1HvtVo3SM8Il3WiZ4nkAlb2BBzKe1TSVKGSyZgD6FW9Bsxom/57ktkqrqmXGA==
3964+
flow-parser@0.116.1:
3965+
version "0.116.1"
3966+
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.116.1.tgz#2cded960269f702552feb5419c57c7c146ba171e"
3967+
integrity sha512-uMbaTjiMhBKa/il1esHyWyVVWfrWdG/eLmG62MQulZ59Yghpa30H1tmukFZLptsBafZ8ddiPyf7I+SiA+euZ6A==
39633968

39643969
follow-redirects@^1.0.0:
39653970
version "1.9.0"
@@ -5929,10 +5934,10 @@ [email protected], lines-and-columns@^1.1.6:
59295934
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
59305935
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
59315936

5932-
linguist-languages@7.6.0:
5933-
version "7.6.0"
5934-
resolved "https://registry.yarnpkg.com/linguist-languages/-/linguist-languages-7.6.0.tgz#6e3d9aa7a98ddf5299f8115788dce8deb8a97c9d"
5935-
integrity sha512-DBZPIWjrQmb/52UlSEN8MTiwwugrAh4NBX9/DyIG8IuO8rDLYDRM+KVPbuiPVKd3ResxYtZB5AiSuc8dTzOSog==
5937+
linguist-languages@7.7.0:
5938+
version "7.7.0"
5939+
resolved "https://registry.yarnpkg.com/linguist-languages/-/linguist-languages-7.7.0.tgz#c069731f8b307ce301980a4bac4b81bf06a2c351"
5940+
integrity sha512-6vR1OgfD/YK1xgqVBT3aUx9zurSVeHUsXpVyTilT1o7LW6MNAcGmWy5wwmQAAhvEXxG2PZmktHbQiGtJ5hpGkg==
59365941

59375942
lint-staged@^9.4.3:
59385943
version "9.4.3"
@@ -6116,12 +6121,7 @@ [email protected]:
61166121
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
61176122
integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=
61186123

6119-
6120-
version "4.7.0"
6121-
resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
6122-
integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=
6123-
6124-
lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15:
6124+
[email protected], lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15:
61256125
version "4.17.15"
61266126
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
61276127
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@@ -7430,16 +7430,16 @@ prettier@^1.16.4, prettier@^1.18.2, prettier@^1.19.1:
74307430
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
74317431
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
74327432

7433-
"prettier@github:prettier/prettier":
7433+
prettier@prettier/prettier:
74347434
version "1.20.0-dev"
7435-
resolved "https://codeload.github.com/prettier/prettier/tar.gz/b7e6d7cf26f25e9b773dd1a778a9f6ed0d574191"
7435+
resolved "https://codeload.github.com/prettier/prettier/tar.gz/96d419b91af957032b1beead6b0ba436ce3b78c5"
74367436
dependencies:
74377437
"@angular/compiler" "8.2.14"
74387438
"@babel/code-frame" "7.5.5"
7439-
"@babel/parser" "7.7.3"
7439+
"@babel/parser" "7.8.3"
74407440
"@glimmer/syntax" "0.41.0"
74417441
"@iarna/toml" "2.2.3"
7442-
"@typescript-eslint/typescript-estree" "2.8.0"
7442+
"@typescript-eslint/typescript-estree" "2.11.0"
74437443
angular-estree-parser "1.1.5"
74447444
angular-html-parser "1.3.0"
74457445
camelcase "5.3.1"
@@ -7448,14 +7448,14 @@ prettier@^1.16.4, prettier@^1.18.2, prettier@^1.19.1:
74487448
cosmiconfig "5.2.1"
74497449
dashify "2.0.0"
74507450
dedent "0.7.0"
7451-
diff "4.0.1"
7451+
diff "4.0.2"
74527452
editorconfig "0.15.3"
74537453
editorconfig-to-prettier "0.1.1"
74547454
escape-string-regexp "1.0.5"
74557455
esutils "2.0.3"
74567456
find-parent-dir "0.3.0"
74577457
find-project-root "1.1.1"
7458-
flow-parser "0.112.0"
7458+
flow-parser "0.116.1"
74597459
get-stream "4.1.0"
74607460
globby "6.1.0"
74617461
graphql "14.5.8"
@@ -7468,8 +7468,8 @@ prettier@^1.16.4, prettier@^1.18.2, prettier@^1.19.1:
74687468
json-stable-stringify "1.0.1"
74697469
leven "3.1.0"
74707470
lines-and-columns "1.1.6"
7471-
linguist-languages "7.6.0"
7472-
lodash.uniqby "4.7.0"
7471+
linguist-languages "7.7.0"
7472+
lodash "4.17.15"
74737473
mem "5.1.1"
74747474
minimatch "3.0.4"
74757475
minimist "1.2.0"
@@ -7484,10 +7484,10 @@ prettier@^1.16.4, prettier@^1.18.2, prettier@^1.19.1:
74847484
regexp-util "1.2.2"
74857485
remark-math "1.0.6"
74867486
remark-parse "5.0.0"
7487-
resolve "1.12.2"
7487+
resolve "1.14.2"
74887488
semver "6.3.0"
74897489
string-width "4.1.0"
7490-
typescript "3.7.2"
7490+
typescript "3.7.5"
74917491
unicode-regex "3.0.0"
74927492
unified "8.4.2"
74937493
vnopts "1.0.2"
@@ -8095,7 +8095,14 @@ [email protected]:
80958095
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
80968096
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
80978097

8098-
[email protected], resolve@^1.10.0, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.8.1:
8098+
8099+
version "1.14.2"
8100+
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2"
8101+
integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==
8102+
dependencies:
8103+
path-parse "^1.0.6"
8104+
8105+
resolve@^1.10.0, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.8.1:
80998106
version "1.12.2"
81008107
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.2.tgz#08b12496d9aa8659c75f534a8f05f0d892fff594"
81018108
integrity sha512-cAVTI2VLHWYsGOirfeYVVQ7ZDejtQ9fp4YhYckWDEkFfqbVjaT11iM8k6xSAfGFMM+gDpZjMnFssPu8we+mqFw==
@@ -9203,7 +9210,12 @@ typescript-workspace-plugin@^2.0.1:
92039210
resolved "https://registry.yarnpkg.com/typescript-workspace-plugin/-/typescript-workspace-plugin-2.0.1.tgz#3d88be1c35a7fdf2c0160c8cf569ca8993439a12"
92049211
integrity sha512-xjIYNFlPIA7IWXvnOFJoAeHPbPJSo0AiQDCRJzaAp3+xZwz6maTgeRLB0oEHVtCqz4Q1CDN6U9kh/2z8sxdDBQ==
92059212

9206-
[email protected], typescript@^3.5.3, typescript@^3.7.2:
9213+
9214+
version "3.7.5"
9215+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
9216+
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
9217+
9218+
typescript@^3.5.3, typescript@^3.7.2:
92079219
version "3.7.2"
92089220
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
92099221
integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==

0 commit comments

Comments
 (0)