Search code examples
typescripterror-handlingasync-awaitkoaecmascript-2016

TypeScript and Koa 2: async/await issue with Global Error Handler


I am writing an app with TypeScript as well as Koa 2.

However, the issue I am experiencing is that my global Koa error handler does not catch errors that were thrown in my application.

Take the following middleware, for example (this is the very first middleware before any routes are loaded):

app.use(async(ctx, next) => {
    console.log("ErrorHandler loaded...");
    try {
        console.log("Trying for error...");
        await next();
    } catch (err) {
        console.log("Caught error...");
        ctx.status = err.status || 500;
        ctx.response.body = "Error: " + err.message;
    }
});

When accessing my routes, I can see that the error handler is loaded and that the try block runs.

However, if I throw an error in a route (irrespective of whether I use throw or ctx.throw), all I get is the default error message "Not found" - so, any errors I throw are never caught and, therefore, my error handler won't handle it.

Now consider the following transpiled JavaScript:

app.use((ctx, next) => __awaiter(this, void 0, void 0, function* () {
    console.log("ErrorHandler loaded...");
    try {
        console.log("Trying for error...");
        yield next();
    }
    catch (err) {
        console.log("Caught error...");
        ctx.status = err.status || 500;
        ctx.response.body = "Error: " + err.message;
    }
}));

The question is two-fold:

  1. Is my application unable to catch thrown errors because of the transpilation? I mean, would it work if the transpiled JavaScript would make use of the async and await keywords, rather than transpiling it to generators using yield?
  2. If the above is correct: is there any way to write Koa 2 apps with TypeScript now?

Edit

I found a "solution" to get my errors to throw, but I still don't understand why Koa didn't catch them.

This for of loop iterates an array of controllers which I dynamically load. For each controller, I am attaching the respective method (action.method) of a class (action.target) to a route (action.route) using the specified Http verb (action.type).

However, I am also binding the context to the method so as to ensure that, as per Koa's conventions, this is bound to the Context:

for (let action of actionMetadata) {
    router[action.type.toLowerCase()](action.route, (ctx, next) => {
        (new action.target)[action.method].bind(ctx)(ctx, next);
    });
}

The above causes the issue where errors are not caught.

In comparison, the below code works: errors are now caught by Koa. But this means that this now is not the Context anymore.

I can live with that since the Context is the first param in my routes, but I don't understand why the error is not caught as all middleware following the error handler (which is my very first middleware) should run within its try block:

for (let action of actionMetadata) {
    router[action.type.toLowerCase()](action.route, (new action.target )[action.method]);
}

Solution

  • Right, looks like this was pure user error in not ensuring that every single middleware returns a promise (or is an async function).

    The async in the following is all that was missing:

    for (let action of actionMetadata) {
        router[action.type.toLowerCase()](action.route, async (ctx, next) => {
            (new action.target)[action.method].bind(ctx)(ctx, next);
        });
    }