Unhandled rejection: Deno’s equivalent of Node.js

Mayank C
Tech Tonic

--

This is a part of the series: Deno’s equivalent of Node.js. In the brief articles that are part of this series, we explore the Deno’s equivalent of commonly used functions in Node.js. The articles are written in no particular order.

In this article, we’ll go over Deno’s equivalent of Node.js’s global error handler: unhandledRejection.

In this particular case, it’d be useful to know that, there is no equivalent in Deno.

Unhandled Rejection

Node.js

In Node.js, an unhandled rejection error/event is raised whenever an error or a rejected promise couldn’t find an error handler.

Consider following HTTP server that does a JSON parsing of request body:

const http = require('http');
http.createServer((req, res) => {
let raw="";
req.on('data', chunk => raw+=chunk);
req.on('end', () => {
const d=JSON.parse(raw);
res.statusCode=200;
res.end();
});
}).listen(5000);

If a client sends an invalid body, the server terminates due to an unhandled error:

> curl http://localhost:5000> node app.js
SyntaxError: Unexpected token u in JSON at position 0
at JSON.parse (<anonymous>)
at IncomingMessage.<anonymous> (/Users/mayankc/Work/source/denoExamples/app.js:6:22)
at IncomingMessage.emit (node:events:406:35)
at endReadableNT (node:internal/streams/readable:1331:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)
>

A try-catch should be wrapped around JSON parsing, and also in all the places an error is expected.

For complex applications in which error handling may get missed (especially with async code like setTimeout, etc.), Node.js also provides a global error handler that can prevent the program from exiting:

process.on('unhandledRejection', e => console.log(e)); //unhandled promise rejections
process.on('uncaughtException', e => console.log(e)); //unhandled errors

With these global error handlers in place, the program won’t crash upon encountering an unhandled error or promise rejection:

const http = require('http');
process.on('unhandledRejection', e => console.log(e));
process.on('uncaughtException', e => console.log(e));
http.createServer((req, res) => {
let raw="";
req.on('data', chunk => raw+=chunk);
req.on('end', () => {
const d=JSON.parse(raw);
res.statusCode=202;
});
}).listen(5000);

--
> curl http://localhost:5000
> curl http://localhost:5000
--> node app.js
SyntaxError: Unexpected token u in JSON at position 0
at JSON.parse (<anonymous>)
at IncomingMessage.<anonymous> (/Users/mayankc/Work/source/denoExamples/app.js:8:22)
at IncomingMessage.emit (node:events:406:35)
at endReadableNT (node:internal/streams/readable:1331:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)
SyntaxError: Unexpected token u in JSON at position 0
at JSON.parse (<anonymous>)
at IncomingMessage.<anonymous> (/Users/mayankc/Work/source/denoExamples/app.js:8:22)
at IncomingMessage.emit (node:events:406:35)
at endReadableNT (node:internal/streams/readable:1331:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)

Ideally, exceptions should be handled by application. The global error handlers are fail-safe, mostly to prevent the app from crashing.

Deno

It’s good to know that there is no such equivalent in Deno. In other words, a Deno app would crash if it encounters an unhandled error or promise rejection. No exceptions :-).

Consider following HTTP server in Deno that does similar JSON parsing of request body:

const listener = Deno.listen({ port: 5000 });
for await(const conn of listener) {
for await(const {request: req, respondWith: res} of Deno.serveHttp(conn)) {
const d=await req.json();
res(new Response());
}
}

If a client sends an invalid body, the server terminates due to an unhandled promise rejection:

> curl http://localhost:5000> deno run --allow-net app.ts
error: Uncaught (in promise) SyntaxError: Unexpected end of JSON input
const d=await req.json();
^
at parse (<anonymous>)
at packageData (deno:extensions/fetch/22_body.js:313:16)
at Request.json (deno:extensions/fetch/22_body.js:253:18)
at async file:///Users/mayankc/Work/source/denoExamples/app.ts:4:17

In Deno, there is no way to define a global handler. The only way out is to handle all the errors. For all incoming requests, there could be a try catch before starting to process it. For async cases like setTimeout, setInterval, etc. a try/catch needs to be present there too. In short, all entry points should be wrapped in a try/catch.

For the above case, an error handler can be defined before processing the request:

const listener = Deno.listen({ port: 5000 });
for await(const conn of listener) {
for await(const {request: req, respondWith: res} of Deno.serveHttp(conn)) {
try {
const d=await req.json();
res(new Response());
} catch(e) {
res(new Response(null, {status: 400}));
}
}
}
--> curl http://localhost:5000
HTTP/1.1 400 Bad Request
content-length: 0

--

--