With the release of Node.js V8, Node.js has natively supported async/await functions, and Koa has also released the official version of Koa 2, which supports async/await middleware, bringing great convenience to handle asynchronous callbacks.

Since Koa 2 already supports async/await middleware, why not just use Koa instead of adapting Express to support Async /await middleware? Because Koa 2 has just been released, and many of the older projects still use Express, it is not possible to rewrite it in Koa because it is too expensive, but to take advantage of the convenience of the new syntax, Express has to be modified, and the modification must be non-intrusive. Otherwise it will cause a lot of trouble.

Use async/await directly

Let’s first look at using the async/await function directly in Express.



const express = require('express');
const app = express();
const { promisify } = require('util');
const { readFile } = require('fs');
const readFileAsync = promisify(readFile);
   
app.get('/'.async function (req, res, next) {
  const data = await readFileAsync('./package.json');
  res.send(data.toString());
});
// Error Handler
app.use(function (err, req, res, next) {
  console.error('Error:', err);
  res.status(500).send('Service Error');
});
   
app.listen(3000.'127.0.0.1'.function () {
  console.log(`Server running at http://The ${this.address().address }:The ${this.address().port }/ `);
});Copy the code

Express is not modified above, and directly uses async/await function to process the request. When requesting http://127.0.0.1:3000/, it is found that the request can be normally requested and the response can also be normally responded. It seems that we can use async/await function directly without any modification to Express. But if an error occurs in async/await function, can it be handled by our error handling middleware? Now let’s read a file that doesn’t exist, such as age.json instead of package.json.



app.get('/'.async function (req, res, next) {
  const data = await readFileAsync('./age.json');
  res.send(data.toString());
});Copy the code

Now we go and askhttp://127.0.0.1:3000/, the request cannot be responded to and eventually times out. The following error is reported on the terminal:



An error was found that was not handled by the error-handling middleware, but instead was thrownunhandledRejectionException, now what if we use try/catch to catch the error manually?



app.get('/'.async function (req, res, next) {
  try {
    const data = await readFileAsync('./age.json');
    res.send(datas.toString());
  } catch(e) { next(e); }});Copy the code

Finding that the request is being handled by the error-handling middleware means that it is ok to catch the error explicitly manually, but adding a try/catch to each middleware or request handler function is too inelegant, intrusive and ugly to the business code. Therefore, through the experiment of directly using async/await function, we found that the direction of Express transformation is to receive the errors thrown by async/await function without being intrusive to business code.

Transform Express

There are two ways to handle routing and middleware in Express. One is an app created through Express, which adds middleware and handles routing directly to the app, as follows:



const express = require('express');
const app = express();
   
app.use(function (req, res, next) {
  next();
});
app.get('/'.function (req, res, next) {
  res.send('hello, world');
});
app.post('/'.function (req, res, next) {
  res.send('hello, world');
});
   
app.listen(3000.'127.0.0.1'.function () {
  console.log(`Server running at http://The ${this.address().address }:The ${this.address().port }/ `);
});Copy the code

The other option is to create a routing instance via the Router of Express and directly add middleware and process routing on the routing instance, as follows:



const express = require('express');
const app = express();
const router = new express.Router();
app.use(router);
   
router.get('/'.function (req, res, next) {
  res.send('hello, world');
});
router.post('/'.function (req, res, next) {
  res.send('hello, world');
});
   
app.listen(3000.'127.0.0.1'.function () {
  console.log(`Server running at http://The ${this.address().address }:The ${this.address().port }/ `);
});Copy the code

Get (‘/’, async function(req, res, next){}), async function(req, res, next){}). For an error to be handled uniformly, of course, next(Err) is called to pass the error to the error-handling middleware, and since async returns promises, AsyncFn ().then().catch(function(err){next(err)})



app.get = function (. data) {
  const params = [];
  for (let item of data) {
    if (Object.prototype.toString.call(item) ! = ='[object AsyncFunction]') {
      params.push(item);
      continue;
    }
    const handle = function (. data) {
      const[ req, res, next ] = data; item(req, res, next).then(next).catch(next); }; params.push(handle); } app.get(... params) }Copy the code

Item (req, res, next).then(next).catch(next); item(req, res, next).catch(next); To catch errors thrown within a function and pass them to error-handling middleware. One obvious error in this code is that app.get() is called last, which is recursive and breaks the functionality of app.get and can’t handle requests at all, so it needs to be modified. We said that the two Express ways of handling routing and middleware can be mixed, so we mixed the two ways to avoid recursion, the code is as follows:



const express = require('express');
const app = express();
const router = new express.Router();
app.use(router);
    
app.get = function (. data) {
  const params = [];
  for (let item of data) {
    if (Object.prototype.toString.call(item) ! = ='[object AsyncFunction]') {
      params.push(item);
      continue;
    }
    const handle = function (. data) {
      const[ req, res, next ] = data; item(req, res, next).then(next).catch(next); }; params.push(handle); } router.get(... params) }Copy the code

Everything seems to work and handle requests properly after the above modification. This breaks the app.get() method, because app.get() can be used not only to handle routing, but also to retrieve the configuration of the application.



methods.forEach(function(method){
  app[method] = function(path){
    if (method === 'get' && arguments.length === 1) {
      // app.get(setting)
      return this.set(path);
    }
    
    this.lazyrouter();
    
    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments.1));
    return this;
  };
});Copy the code

Therefore, we also need to do special treatment for app.get during the transformation. In the actual application, we have not only GET requests, but also POST, PUT, and DELETE requests, so our final modified code is as follows:



const { promisify } = require('util');
const { readFile } = require('fs');
const readFileAsync = promisify(readFile);
const express = require('express');
const app = express();
const router = new express.Router();
const methods = [ 'get'.'post'.'put'.'delete' ];
app.use(router);
    
for (let method of methods) {
  app[method] = function (. data) {
    if (method === 'get' && data.length === 1) return app.set(data[0]);

    const params = [];
    for (let item of data) {
      if (Object.prototype.toString.call(item) ! = ='[object AsyncFunction]') {
        params.push(item);
        continue;
      }
      const handle = function (. data) {
        const[ req, res, next ] = data; item(req, res, next).then(next).catch(next); }; params.push(handle); } router[method](... params); }; } app.get('/'.async function (req, res, next) {
  const data = await readFileAsync('./package.json');
  res.send(data.toString());
});
      
app.post('/'.async function (req, res, next) {
  const data = await readFileAsync('./age.json');
  res.send(data.toString());
});
    
router.use(function (err, req, res, next) {
  console.error('Error:', err);
  res.status(500).send('Service Error');
}); 
     
app.listen(3000.'127.0.0.1'.function () {
  console.log(`Server running at http://The ${this.address().address }:The ${this.address().port }/ `);
});Copy the code

Now we are done, we just need to add a small piece of code, we can directly use async function as a handler request, non-invasive to the business, throw errors can also be passed to the error processing middleware.

Let Express support async/await