未验证 提交 2d207fb1 编写于 作者: S Shu Ding 提交者: GitHub

Dedupe in-flight server data requests (#22781)

This PR adds request deduplication for `_getServerData`. If a request with the same URL is already in-flight, we don't send another new request. When a request succeeds or fails, we delete the cache.

A potential improvement this brings is, when `getServerSideProps` of a new route is slow to load, the user might keep clicking on the link which causes new requests, and the route will never update because results of old requests were ditched. Also adds a test case for this scenario.

Closes #19238.
上级 afc8ce4d
......@@ -503,6 +503,9 @@ export default class Router implements BaseRouter {
components: { [pathname: string]: PrivateRouteInfo }
// Static Data Cache
sdc: { [asPath: string]: object } = {}
// In-flight Server Data Requests, for deduping
sdr: { [asPath: string]: Promise<object> } = {}
sub: Subscription
clc: ComponentLoadCancel
pageLoader: any
......@@ -1598,7 +1601,19 @@ export default class Router implements BaseRouter {
}
_getServerData(dataHref: string): Promise<object> {
return fetchNextData(dataHref, this.isSsr)
const { href: resourceKey } = new URL(dataHref, window.location.href)
if (this.sdr[resourceKey]) {
return this.sdr[resourceKey]
}
return (this.sdr[resourceKey] = fetchNextData(dataHref, this.isSsr)
.then((data) => {
delete this.sdr[resourceKey]
return data
})
.catch((err) => {
delete this.sdr[resourceKey]
throw err
}))
}
getInitialProps(
......
......@@ -104,7 +104,7 @@ describe('Build Output', () => {
expect(parseFloat(err404FirstLoad)).toBeCloseTo(67.1, 0)
expect(err404FirstLoad.endsWith('kB')).toBe(true)
expect(parseFloat(sharedByAll)).toBeCloseTo(63.8, 1)
expect(parseFloat(sharedByAll)).toBeCloseTo(63.9, 1)
expect(sharedByAll.endsWith('kB')).toBe(true)
if (_appSize.endsWith('kB')) {
......
......@@ -37,6 +37,10 @@ const Page = ({ world, time, url }) => {
<a id="normal">to normal</a>
</Link>
<br />
<Link href="/slow">
<a id="slow">to slow</a>
</Link>
<br />
<Link href="/blog/[post]" as="/blog/post-1">
<a id="post-1">to dynamic</a>
</Link>
......
import Link from 'next/link'
let serverHitCount = 0
export async function getServerSideProps() {
await new Promise((resolve) => setTimeout(resolve, 500))
return {
props: {
count: ++serverHitCount,
},
}
}
export default ({ count }) => (
<>
<p>a slow page</p>
<p id="hit">hit: {count}</p>
<Link href="/">
<a id="home">to home</a>
</Link>
<br />
<Link href="/something">
<a id="something">to something</a>
</Link>
</>
)
......@@ -142,6 +142,12 @@ const expectedManifestRoutes = () => [
),
page: '/refresh',
},
{
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(buildId)}\\/slow.json$`
),
page: '/slow',
},
{
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(buildId)}\\/something.json$`
......@@ -616,6 +622,32 @@ const runTests = (dev = false) => {
expect(curRandom).toBe(initialRandom + '')
})
it('should dedupe server data requests', async () => {
const browser = await webdriver(appPort, '/')
await waitFor(2000)
// Keep clicking on the link
await browser.elementByCss('#slow').click()
await browser.elementByCss('#slow').click()
await browser.elementByCss('#slow').click()
await browser.elementByCss('#slow').click()
await check(() => getBrowserBodyText(browser), /a slow page/)
// Requests should be deduped
const hitCount = await browser.elementByCss('#hit').text()
expect(hitCount).toBe('hit: 1')
// Should send request again
await browser.elementByCss('#home').click()
await browser.waitForElementByCss('#slow')
await browser.elementByCss('#slow').click()
await check(() => getBrowserBodyText(browser), /a slow page/)
const newHitCount = await browser.elementByCss('#hit').text()
expect(newHitCount).toBe('hit: 2')
})
if (dev) {
it('should not show warning from url prop being returned', async () => {
const urlPropPage = join(appDir, 'pages/url-prop.js')
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册