Skip to content

Commit a40aa81

Browse files
committed
feat: rewrite promisify logic
1 parent 4d39ec7 commit a40aa81

File tree

11 files changed

+396
-125
lines changed

11 files changed

+396
-125
lines changed

README.md

Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# h2
22

3-
[![npm](https://img.shields.io/npm/dm/h-2.svg?style=flat-square)](https://npmjs.com/package/h-2)
4-
[![npm (scoped with tag)](https://img.shields.io/npm/v/h-2/latest.svg?style=flat-square)](https://npmjs.com/package/h-2)
3+
[![npm](https://img.shields.io/npm/dm/@nuxt/h2.svg?style=flat-square)](https://npmjs.com/package/@nuxt/h2)
4+
[![npm (scoped with tag)](https://img.shields.io/npm/v/@nuxt/h2/latest.svg?style=flat-square)](https://npmjs.com/package/@nuxt/h2)
55

66
> h2 is a 3 kB modern and environment agnostic http framework
77
@@ -11,8 +11,8 @@
1111
- Compatibile with connect/express middleware
1212
- Tree-shakable and zero dependency
1313
- Promise and `aync/await` support
14-
- Lazy loading middleware
15-
- Basic router
14+
- Lazy loading
15+
- Basic Router
1616

1717
> See [un](https://github.com/nuxt-contrib/un) for workers support
1818
@@ -31,13 +31,14 @@ const { Server } = require('http')
3131
const { createApp } = require('@nuxt/h2')
3232

3333
const app = createApp()
34-
app.use('/', () => 'Hello world!')
34+
3535
app.use('/api', (req) => ({ url: req.url }))
36+
app.use('/', () => 'Hello world!')
3637

3738
const port = process.env.PORT || 3000
3839
const server = new Server(app.handle)
3940
server.listen(port, () => {
40-
console.log(`Listening on: http://localhost:${port}`
41+
console.log(`Listening on: http://localhost:${port}`)
4142
})
4243
```
4344

@@ -48,44 +49,6 @@ server.listen(port, () => {
4849
- `redirect (req, location, code)`
4950
- `lazy (handle)`
5051

51-
## `promisifyHandle`
52-
53-
Optionally you can use express or your own middleware system by directly using this utility and not using app stack.
54-
55-
Converts handle/middleware into a promisified async version:
56-
57-
- Ensures if handle is an `async` function or returning `Promise`, error is passed to the next middleware
58-
- If middleware supports a third param (`next` callback) it will be piped to the promise chain
59-
- If middleware returns or calls `next(null, value)` it will be piped to the promise chain
60-
- Otherwise resolves promise to `response.writableEnded` to indicate response is ended or not
61-
62-
**Why?**
63-
64-
- Avoid unhandled promise rejections since developers usually create `async` middleware without handling errors
65-
- Allow directly using any middleware (like `serve-static`) by awaiting on it
66-
- Convert vanilla http server into a modern handler supporting async and json!
67-
68-
**Example:** Register middleware and ensure it is safe using async operations
69-
70-
```js
71-
const express = require('express')
72-
73-
const app = express()
74-
75-
const test = async (req, res) => {
76-
await Promise.reject(new Error('oops!'))
77-
}
78-
79-
app.use('/api/test', promisifyHandle(test))
80-
```
81-
82-
**Example:** Use `serve-static` as an awaitable utility
83-
84-
```js
85-
const serveStatic = promisifyHandle(require('serve-static'))
86-
const served = await serveStatic(req, res)
87-
```
88-
8952
## License
9053

9154
MIT

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@nuxtjs/eslint-config-typescript": "latest",
2121
"@types/node": "latest",
2222
"eslint": "latest",
23+
"express": "^4.17.1",
2324
"jest": "^26.6.3",
2425
"siroc": "latest",
2526
"standard-version": "latest",

src/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ export function createApp (): App {
1818
}
1919
req.url = originalUrl.substr(layer.route.length)
2020
}
21-
// Direct return support
21+
2222
const val = await layer.handle(req, res)
23+
2324
const type = typeof val
2425
if (type === 'string') {
2526
send(res, val, MIMES.html)

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './app'
22
export * from './promisify'
33
export * from './types'
44
export * from './utils'
5+
export * from './lazy'

src/lazy.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { IncomingMessage, ServerResponse } from 'http'
2+
import { LazyHandle, Handle } from './types'
3+
4+
export function lazy (handle: LazyHandle): Handle {
5+
let _promise: Promise<Handle>
6+
7+
const resolve = () => {
8+
if (!_promise) {
9+
_promise = Promise.resolve(handle()).then((r: any) => r.default || r)
10+
}
11+
return _promise
12+
}
13+
14+
return function (req: IncomingMessage, res: ServerResponse) {
15+
return resolve().then(h => h(req, res))
16+
}
17+
}

src/promisify.ts

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,25 @@
11
import type { IncomingMessage, ServerResponse } from 'http'
2-
import type { Handle, PHandle, NextFn } from './types'
2+
import type { Handle, Middleware } from './types'
33

4-
export function promisifyHandle (handle: Handle): PHandle {
5-
const hasNext = handle.length >= 3 /* req,res,next */
6-
if (hasNext) {
7-
return function _promisified (req, res, next) {
8-
return callHandle(req, res, handle, next)
9-
}
10-
} else {
11-
return function _promisified (req, res) {
12-
return callHandle(req, res, handle)
13-
}
4+
export function promisifyHandle (handle: Handle | Middleware): Handle {
5+
return function (req: IncomingMessage, res: ServerResponse) {
6+
return callHandle(handle as Handle, req, res)
147
}
158
}
169

17-
function callHandle (req: IncomingMessage, res: ServerResponse, handle: Handle, next?: NextFn) {
18-
const promise = new Promise((resolve, reject) => {
19-
const _next = next ? (err?: Error, val?: any) => err ? reject(err) : resolve(val) : undefined
20-
let _promise
10+
function callHandle (handle: Middleware, req: IncomingMessage, res: ServerResponse) {
11+
return new Promise((resolve, reject) => {
12+
const next = (err?: Error) => err ? reject(err) : resolve()
2113
try {
22-
_promise = handle(req, res, _next)
14+
const returned = handle(req, res, next)
15+
if (returned !== undefined) {
16+
resolve(returned)
17+
} else {
18+
res.once('close', next)
19+
res.once('error', next)
20+
}
2321
} catch (err) {
24-
reject(err)
25-
}
26-
if (!next) {
27-
resolve(_promise)
22+
next(err)
2823
}
2924
})
30-
return promise
31-
.then((val?: any) => next ? next() : (val === undefined ? res.writableEnded : val))
32-
.catch(err => next && next(err))
3325
}

src/types.ts

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import type { IncomingMessage, ServerResponse } from 'http'
22

3-
export type NextFn = (err?: Error) => any
3+
export type Handle = (req: IncomingMessage, res: ServerResponse) => any
44

5-
export type Handle = (req: IncomingMessage, res: ServerResponse, next?: NextFn) => any
6-
7-
export type PHandle = (req: IncomingMessage, res: ServerResponse, next?: NextFn) => Promise<any>
5+
export type Middleware = (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => any) => any
86

97
export interface Layer {
108
route: string
11-
handle: PHandle
9+
handle: Handle
1210
}
1311

1412
export type Stack = Layer[]
@@ -22,23 +20,3 @@ export interface App {
2220
handle: Handle
2321
use: (route: string, handle: Handle) => void
2422
}
25-
26-
export type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le'
27-
| 'ucs2' | 'ucs-2' | 'base64' | 'latin1' | 'binary' | 'hex'
28-
29-
export type Callback<E = Error | null | undefined> = (error?: E) => void
30-
31-
export type HeadersObject = { [key: string]: string | string[] | undefined }
32-
33-
export interface CallContext {
34-
url?: string
35-
method?: string
36-
headers?: HeadersObject
37-
host?: string
38-
protocol?: 'http' | 'https' | string
39-
40-
req?: IncomingMessage
41-
res?: ServerResponse
42-
43-
[key: string]: any
44-
}

src/utils.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { ServerResponse } from 'http'
2-
import { LazyHandle, Handle } from './types'
32

43
export const MIMES = {
54
html: 'text/html',
@@ -37,14 +36,3 @@ export function redirect (res: ServerResponse, location: string, code = 302) {
3736
defaultContentType(res, MIMES.html)
3837
res.end(location)
3938
}
40-
41-
export function lazy (handle: LazyHandle): Handle {
42-
let _promise: Promise<Handle>
43-
const resolve = () => (
44-
_promise = _promise ||
45-
Promise.resolve(handle()).then((r: any) => r.default || r)
46-
)
47-
return function _lazy (req, res) {
48-
return resolve().then(h => h(req, res))
49-
}
50-
}

test/fixture/server.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const { Server } = require('http')
2+
const express = require('express')
3+
const { createApp } = require('../..')
4+
5+
function createExpress () {
6+
const app = express()
7+
app.use('/', (_req, res) => {
8+
res.json({ express: 'works' })
9+
})
10+
return app
11+
}
12+
13+
const app = createApp()
14+
15+
app.use('/api/hello', req => ({ url: req.url }))
16+
app.use('/api/express', createExpress())
17+
app.use('/', () => 'Hello world!')
18+
19+
const port = process.env.PORT || 3000
20+
const server = new Server(app.handle)
21+
server.listen(port, () => {
22+
console.log(`Listening on: http://localhost:${port}`)
23+
})

test/utils.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)