Last active
September 18, 2023 15:58
-
-
Save nicolasparada/bfa29816099c8e8ec25e95152de79abc to your computer and use it in GitHub Desktop.
Newline Delimited JSON (NDJSON) Demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const abortController = new AbortController() | |
fetch('/api/ndjson', { | |
headers: { accept: 'application/x-ndjson' }, | |
signal: abortController.signal, | |
}).then(async res => { | |
const reader = res.body.getReader() | |
const textDecoder = new TextDecoder() | |
try { | |
while (true) { | |
const { done, value } = await reader.read() | |
if (done) { | |
break | |
} | |
const text = textDecoder.decode(value) | |
const json = JSON.parse(text) | |
console.log(json) | |
} | |
} finally { | |
reader.releaseLock() | |
} | |
}).catch(err => { | |
if (err.name === 'AbortError') { | |
return | |
} | |
console.error(err) | |
}) | |
document.querySelector('button').onclick = () => { | |
abortController.abort() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>NDJSON Demo</title> | |
<link rel="shortcut icon" href="data:,"> | |
<script src="/client.js" defer></script> | |
</head> | |
<body> | |
<pre>Check console.</pre> | |
<button>Cancel</button> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"private": true, | |
"engines": { | |
"node": ">= 10.1.0" | |
}, | |
"scripts": { | |
"start": "node -r esm server.js" | |
}, | |
"dependencies": { | |
"@nicolasparada/httptools": "~0.5.0", | |
"esm": "^3.0.0", | |
"serve-handler": "^3.2.0" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createRouter } from '@nicolasparada/httptools'; | |
import * as http from 'http'; | |
import * as process from 'process'; | |
import serve from 'serve-handler'; | |
const router = createRouter() | |
router.handle('GET', '/api/ndjson', ndjsonHandler) | |
router.handle('GET', '/*', staticHandler) | |
const server = http.createServer(router.requestListener) | |
const port = process.env.PORT || 3000 | |
server.listen(port, err => { | |
if (err) { | |
console.error(err) | |
return | |
} | |
console.log(`server running at http://localhost:${port}/`) | |
}) | |
/** | |
* @param {http.IncomingMessage} req | |
* @param {http.ServerResponse} res | |
*/ | |
async function ndjsonHandler(req, res) { | |
if (!accepts(req, 'application/x-ndjson')) { | |
respondText(res, 'Not Acceptable', 406) | |
return | |
} | |
res.setHeader('Content-Type', 'application/x-ndjson') | |
for await (const now of genEvery(Date.now, 1000)) { | |
// @ts-ignore | |
if (req.aborted) { | |
break | |
} | |
const ndjson = JSON.stringify({ now }) + '\n' | |
res.write(ndjson) | |
} | |
res.end() | |
} | |
/** | |
* @param {http.IncomingMessage} req | |
* @param {http.ServerResponse} res | |
*/ | |
function staticHandler(req, res) { | |
serve(req, res, { public: 'static' }) | |
} | |
/** | |
* @param {http.IncomingMessage} req | |
* @param {string} contentType | |
*/ | |
function accepts(req, contentType) { | |
if (typeof req.headers.accept !== 'string') { | |
return false | |
} | |
if (req.headers.accept.includes('*/*')) { | |
return true | |
} | |
const [type] = contentType.split('/') | |
if (req.headers.accept.includes(type + '/*')) { | |
return true | |
} | |
return req.headers.accept.includes(contentType) | |
} | |
/** | |
* @param {http.ServerResponse} res | |
* @param {string} text | |
* @param {number=} statusCode | |
*/ | |
function respondText(res, text, statusCode = 200) { | |
res.statusCode = statusCode | |
res.setHeader('Content-Type', 'text/plain; charset=utf-8') | |
res.end(text) | |
} | |
/** | |
* @param {function(): any} fn | |
* @param {number=} ms | |
*/ | |
async function* genEvery(fn, ms = 0) { | |
yield fn() | |
while (true) { | |
await delay(ms) | |
yield fn() | |
} | |
} | |
/** | |
* @param {number=} ms | |
*/ | |
function delay(ms = 0) { | |
return new Promise(resolve => { | |
setTimeout(resolve, ms) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment