Skip to content

Commit 962c6ab

Browse files
aquapiv1rtlclaude
authored
perf: improvements for request handling (#487)
* perf: improvements for request handling - avoid allocating a closure & a promise if handler is not asynchronous for applyHandler() - avoid allocating a promise in handle(middleware, pathname) - Change "if (a.b == null) a.b = x" to "a.b ??= x" and similar patterns * fix: add type assertion for async handler return type TypeScript doesn't narrow the Handler union type based on the Symbol.toStringTag runtime check, so we need to cast the result to Promise<void> when calling .catch(). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: changeset --------- Co-authored-by: v1rtl <hi@v1rtl.site> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2873bfb commit 962c6ab

2 files changed

Lines changed: 30 additions & 31 deletions

File tree

.changeset/good-ears-hunt.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@tinyhttp/router": patch
3+
"@tinyhttp/jsonp": patch
4+
"@tinyhttp/app": patch
5+
---
6+
7+
Improve performance of the router and app

packages/app/src/app.ts

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,15 @@ const trail = (x: string) => (x.charCodeAt(x.length - 1) === 47 ? x.substring(0,
2323

2424
const mount = (fn: App | Handler) => (fn instanceof App ? fn.attach : fn)
2525

26-
const applyHandler =
27-
<Req, Res>(h: Handler<Req, Res>) =>
28-
async (req: Req, res: Res, next: NextFunction) => {
29-
try {
30-
if (h[Symbol.toStringTag] === 'AsyncFunction') {
31-
await h(req, res, next)
32-
} else h(req, res, next)
33-
} catch (e) {
34-
next?.(e)
35-
}
26+
const applyHandler = <Req, Res>(h: Handler<Req, Res>, req: Req, res: Res, next: NextFunction) => {
27+
if (h[Symbol.toStringTag] === 'AsyncFunction') return (h(req, res, next) as Promise<void>).catch(next)
28+
29+
try {
30+
h(req, res, next)
31+
} catch (e) {
32+
next?.(e)
3633
}
34+
}
3735

3836
// Shared middleware for handling HEAD requests - avoids creating new object per request
3937
const HEAD_HANDLER_MW: Middleware = {
@@ -99,8 +97,7 @@ export class App<Req extends Request = Request, Res extends Response = Response>
9997
...options.settings
10098
}
10199
if (options.applyExtensions) this.applyExtensions = options?.applyExtensions
102-
const boundHandler = this.handler.bind(this)
103-
this.attach = (req, res, next?: NextFunction) => setImmediate(boundHandler, req, res, next)
100+
this.attach = this.handler.bind(this)
104101
this.cache = {}
105102
}
106103

@@ -112,12 +109,11 @@ export class App<Req extends Request = Request, Res extends Response = Response>
112109

113110
enable<K extends keyof AppSettings>(setting: K): this {
114111
this.settings[setting] = true as AppSettings[K]
115-
116112
return this
117113
}
118114

119115
enabled<K extends keyof AppSettings>(setting: K) {
120-
return Boolean(this.settings[setting])
116+
return !!this.settings[setting]
121117
}
122118

123119
disable<K extends keyof AppSettings>(setting: K): this {
@@ -154,9 +150,8 @@ export class App<Req extends Request = Request, Res extends Response = Response>
154150

155151
locals = { ...locals, ...data }
156152

157-
if (opts.cache == null) opts.cache = this.enabled('view cache')
158-
159-
if (opts.cache) {
153+
// biome-ignore lint/suspicious/noAssignInExpressions: its faster this way
154+
if ((opts.cache ??= this.enabled('view cache'))) {
160155
view = this.cache[name] as View
161156
}
162157

@@ -261,10 +256,9 @@ export class App<Req extends Request = Request, Res extends Response = Response>
261256

262257
#find(url: string, method: string, exclude: Middleware[]): Middleware<Req, Res>[] {
263258
const result: Middleware<Req, Res>[] = []
264-
const isHead = method === 'HEAD'
265259

266-
for (let i = 0; i < this.middleware.length; i++) {
267-
const m = this.middleware[i]
260+
for (let i = 0, middlewares = this.middleware, isHead = method === 'HEAD'; i < middlewares.length; i++) {
261+
const m = middlewares[i]
268262

269263
// Regex is pre-compiled at registration time in pushMiddleware
270264
if (!m.regex?.pattern.test(url)) {
@@ -312,7 +306,7 @@ export class App<Req extends Request = Request, Res extends Response = Response>
312306

313307
req.baseUrl = ''
314308

315-
const handle = async (middleware: Middleware, pathname: string) => {
309+
const handle = (middleware: Middleware, pathname: string) => {
316310
const { path, handler, regex } = middleware
317311

318312
let params: URLParams
@@ -336,19 +330,18 @@ export class App<Req extends Request = Request, Res extends Response = Response>
336330
}
337331
}
338332

339-
if (!req.params) req.params = {}
340-
Object.assign(req.params, params)
333+
// biome-ignore lint/suspicious/noAssignInExpressions: its faster this way
334+
Object.assign((req.params ??= {}), params)
341335

342336
if (middleware.type === 'mw') {
343-
req.url = lead(req.originalUrl.substring(prefix.length))
344-
req.baseUrl = trail(req.originalUrl.substring(0, prefix.length))
337+
req.url = lead(req.originalUrl.slice(prefix.length))
338+
req.baseUrl = trail(req.originalUrl.slice(0, prefix.length))
345339
}
346340

347-
if (!req.path) req.path = pathname
348-
341+
req.path ??= pathname
349342
if (this.settings?.enableReqRoute) req.route = middleware
350343

351-
await applyHandler<Req, Res>(handler as unknown as Handler<Req, Res>)(req, res, next as NextFunction)
344+
return applyHandler<Req, Res>(handler as unknown as Handler<Req, Res>, req, res, next as NextFunction)
352345
}
353346

354347
let idx = 0
@@ -363,8 +356,7 @@ export class App<Req extends Request = Request, Res extends Response = Response>
363356
idx = mw.length
364357
req.params = {}
365358
}
366-
mw.push(...matched)
367-
mw.push(HEAD_HANDLER_MW)
359+
mw.push(...matched, HEAD_HANDLER_MW)
368360
} else if (this.parent == null) {
369361
mw.push({
370362
handler: this.noMatchHandler,
@@ -385,7 +377,7 @@ export class App<Req extends Request = Request, Res extends Response = Response>
385377

386378
if (res.writableEnded) return
387379
if (idx >= mw.length) {
388-
if (parentNext != null) parentNext()
380+
parentNext?.()
389381
return
390382
}
391383

0 commit comments

Comments
 (0)