Every time I don’t give up on the strange problems that pop up in my little head, I get a lot more than just knowledge. Here’s food for thought on why Express can’t define middleware at the front like Koa does, and catch exceptions globally.

1. The observation

Have those of you who have used Express’s scaffolding, Express-Generator, and Koa’s KoA-Generator to create projects looked closely at how errors are caught? What? You don’t. Well, I do. Let me elaborate on the ~

1.1 Express error capture

In Express it is handled using a middleware that receives four parameters, as follows:

app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'dev' ? err : {};
​
  // render the error page
  res.status(err.status || 500);
  res.render('error');
});
Copy the code

This middleware is the last middleware. When other middleware runs, if any error is reported, it will use next to pass the error out, and this middleware can catch it and process it.

Roughly as follows:

app.get('/', function(req, res,next){
    try{
        ......
        next();
    } catch (err){
        next(err)
    }
});
Copy the code

However, this requires a lot of repetitive code in each middleware try catch, and then we’ll see what Koa error handling looks like. No comparison, no harm

1.2 Koa error capture

Koa-generator creates a project using a tool called koa-onError, which is at the top of the list, onError (app). This is a tool made by someone else. How can I observe it? Only a hundred stars and nothing to gain. Then I went online and looked up Koa’s error handling mechanism, and I was on to something.

We can write our own error-catching middleware and put it first, like this:

const catchError = async(ctx, next) => {
    try{
        await next();
    }catch(error){
        ctx.body = error.msg;
    }
}
app.use(catchError)
Copy the code

App.use (catchError) can catch errors in middleware executed after app.use(catchError) can catch errors in middleware executed after app.use(catchError).

1.3 Confusion

But I still look confused. Nani? Why should this catch middleware errors globally? I guess that the execution of each middleware is connected by next. Mr. Shuangyue has learned a little about the middleware principle of Koa. Next represents the middleware of the next implementation, so next() is executing the middleware, that is, the next middleware is executed in the previous middleware. Middleware is nested with each other. I’m trying and catching at the front, which is basically trying and catching all the middleware. Could that be the reason?

2. Think about

Is it really the reason mentioned above? It occurred to me that the interface between Express middleware also uses next! Middleware is also nested within each other. EXpress error capture is not written like this.

2.1 Another puzzle

At this time, I also remembered another doubt of mine. When everyone was learning Koa’s Onion model, did the teacher write codes like the following to explain what the Onion model is

Const Koa = require(' Koa ') const app = new Koa() app.use(async (CTX, next) => {console.log(' I am the first middleware to start ') await next(); Console. log(' I was the first middleware end ')}); App.use (async (CTX, next) => {console.log(' I am the second middleware to start ') await next(); Console. log(' I am the second middleware end ')}); App.use (async (CTX, next) => {console.log(' I am the third middleware to start ') await next(); Console. log(' I am the third middleware end ')}); app.use(async ctx => { ctx.res.end ('hello ya'); }); app.listen(8000);Copy the code

The terminal output is as follows:

Then the teacher will say, this is the onion model, wrapped and nested layer by layer, but brothers, without involving async and await, Express can also print the same result according to the above logic, you must try it yourself. No one ever says that Express’s middleware mechanism is the Onion model.

2.2 Attempting to write Express global error capture

I have always believed that practice is the only criterion for testing truth. I wrote a short test code for Express following the logic of writing error capture for Koa, as follows:

var express = require('express'); var app = express(); Const catchError = (res,req, next) => {console.log(' catch start ') try{next(); }catch(error){console.log(' catch error ')} console.log(' catch error ')} app.use(catchError) app.use((req,res,next) => { Console. log(' first middleware starts ') next() console.log(' first middleware ends ')}) app.use((req,res,next) => {console.log(' second middleware starts ') var error = new Error(); error.errorCode = 1000; Error. MSG = "sorry, I was wrong "; Throw error console.log(' second middleware ends ')}) app.listen(3000);Copy the code

The console prints the following:

As you can see, no errors were caught, just the statement following the throw error was not printed, and the error-catching middleware did not take effect at all. I have posted my test code for fear that I have made a mistake in writing the test code. If in fact, I could have written it for Express according to the logic of writing error capture for Koa. That would be awkward, all the rest of it is wrong, so save a hand, I hope you can help me look.

2.3 Reexamine Koa’s Onion model

Thinking can not give a result, we look for information, hang a ladder, Google, fortunately, still found the answer. It turns out that understanding the onion model and the response are much better.

  • In Express we are passingres.sendThe response to the client’s data is immediate, not in multiple middlewareres.send
  • In Koa we passedctx.bodyYou can modify it in multiple middleware, and only after all middleware has executed will it respond to the client

In Koa, requests and responses are in the outermost layer, while middleware processes layer by layer in the middle. Attached are the stolen model drawings:

Global capture in 2.4 Express

A new understanding of the Onion model solved one of the doubts, but it seems that the original problem has not been solved, why Koa can put a middleware in the front to catch errors, but Express cannot. I also looked for information on how to catch errors globally in Express. I saw some of the answers and the basic principle is to try and catch each middleware, and then pass the error through next to the middleware that handles the error. It’s just encapsulated. Something like this:

const asyncHandler = fn => (req, res, next) =>
  Promise.resolve()
    .then(() => fn(req, res, next))
    .catch(next);
​
router.get('/', asyncHandler(async (req, res) => {
  const user = await db.userInfo();
  res.json(user);
}));
Copy the code

Or use the tool Express-async-errors. I heard that its principle is the same as above, except for the routing layer. I don’t quite understand it.

3. Solve

Sorry, I can’t solve it

Originally the result of this blog should be above so, but met big guy, a fierce discussion in the group, I may finally understand why! Really, directly touched.

As mentioned earlier, Express often uses Next to pass errors, and at the end you can execute a middleware that takes four parameters (including error) to handle errors. Because of this encapsulation, we can’t catch exceptions globally in the first place like in Koa. Because each middleware contains its own try and catch logic, Express middleware does this after catching an error:

As you can see, after it catches the exception itself, thecatchIt’s going to be passed directly tonextBy default, it is handed over to the middleware it encapsulates. So you put another layer on top of ittrycatchErrors are no longer caught.

Then let’s take a look at how Koa’s middleware handles this:

As you can see, there’s a try and catch, but in a catch it’s a Promise. Reject (err) that throws the error again, and the outer try, the catch, is caught. So it doesn’t have much to do with the onion model. Try and catch is possible because of the nested structure of the middleware code

At the end of the day, Koa is lightweight and doesn’t have as much built-in middleware as Express.

Next, I will attach the test code for Koa and Express. If you are interested, just put a breakpoint on it and do it in one step.

Express:

Use (function (req,res,next) {try {console.log(' first middleware start ') Next () console.log(' first middleware end ')} Catch (error){console.log(' Sorry, Error)}}) app.use(function(req,res,next) {throw new error (' error ')}) app.listen(4000)Copy the code

Koa:

const koa = require("koa") const app = new koa(); const catchError = async(ctx, next) => { try{ await next(); }catch(error){console.log(' catch: ',error)}} app.use(catchError) Next) => {console.log(' I am the first time to start ') await next() console.log(' I am the first time to finish ')}) app.use(async (CTX, Next) => {throw Error(' sorry, I'm wrong ')// make a breakpoint}) app.listen(3000)Copy the code

4. To summarize

To be honest, I have been confused about this problem for almost three days. I have done various tests, including Baidu and Google, and also asked a lot of places, but everyone seems to have never thought about staying in such a small place. Maybe my attention is strange, and finally I was able to solve it with the help of everyone. Very grateful, without stopping to write this blog, to everyone.

Reference:

The middleware mechanism of KOA and Express revealed

Multidimensional analysis of the differences between Express and Koa