基于Nodejs平台web框架Express-VS-Koa

42次阅读

共计 3674 个字符,预计需要花费 10 分钟才能阅读完成。

Express 和 Koa 都是基于 Nodejs 平台的 web 框架,也是目前比较常见的用于快速开发 web 服务的框架,且两者都是基于 middleware 的方式去处理客户端请求,那么两者有何区别呢?
简单点说就是,“Express 是直线型,Koa 是洋葱模型 ”。(喊口号!!!)
我们先来看看下面的示例代码:

// for express example
const express = require('express');

const app = express();

function cb1(req, res, next) {console.log('>>>>>>cb1');
    next();
    console.log('<<<<<<cb1');
}

function cb2(req, res, next) {console.log('>>>cb2<<<');
    res.send('hello world');
}

app.use('/', [cb1, cb2]);
app.listen(3000);
// for koa2 example
const koa = require('koa2');

const app = koa();

function cb1(ctx, next) {console.log('>>>>>>cb1');
    next();
    console.log('<<<<<<cb1');
}

function cb2(ctx, next) {console.log('>>>cb2<<<');
    ctx.body = 'hello world';
}

app.use(cb1);
app.use(cb2);
app.listen(3000);

以上两段代码的输出皆为:

>>>>>>cb1
>>>cb2<<<
<<<<<<cb1

所以,当 middleware 为同步函数时,两者从执行结果上来看并无区别。
我们再来看看下面的示例代码:

// for express example
const express = require('express');

const app = express();

async function cb1(req, res, next) {console.log('>>>>>>cb1');
    await next();
    console.log('<<<<<<cb1');
}

async function cb2(req, res, next) {return new Promise((resolve) => {setTimeout(resolve, 500);
    }).then(() => {console.log('>>>cb2<<<');
        res.send('hello world');
    });
}

app.use('/', [cb1, cb2]);
app.listen(3000);
// for koa2 example
const koa = require('koa2');

const app = new koa();

async function cb1(ctx, next) {console.log('>>>>>>cb1');
    await next();
    console.log('<<<<<<cb1');
}

async function cb2(ctx, next) {return new Promise((resolve) => {setTimeout(resolve, 500);
    }).then(() => {console.log('>>>cb2<<<');
        ctx.body = 'hello world';
    });
}

app.use(cb1);
app.use(cb2);
app.listen(3000);

express-example 的输出为:

>>>>>>cb1
>>>>>>cb1
>>>cb2>>>

而 koa2-example 的输出为:

>>>>>>cb1
>>>cb2<<<
<<<<<<cb1

从上面的例子可以看出,当 middleware 为异步函数时,Express 和 Koa 的执行流程是不同的。Express 的返回结果并不是我们设想中的结果,是什么原因导致的行为差异呢?
下面,让我们一起来简单的分析下 Express 和 Koa 中执行 middleware 部分的源码片段。
在 Express 中,执行 middleware 的逻辑代码主要位于 lib/router/route.jslib/router.layer.js文件:

// route.js
Route.prototype.dispatch = function dispatch(req, res, done) {
  var idx = 0;
  var stack = this.stack;
  if (stack.length === 0) {return done();
  }

  var method = req.method.toLowerCase();
  if (method === 'head' && !this.methods['head']) {method = 'get';}

  req.route = this;

  next();

  function next(err) {
    // signal to exit route
    if (err && err === 'route') {return done();
    }

    // signal to exit router
    if (err && err === 'router') {return done(err)
    }

    var layer = stack[idx++];
    if (!layer) {return done(err);
    }

    if (layer.method && layer.method !== method) {return next(err);
    }

    if (err) {layer.handle_error(err, req, res, next);
    } else {layer.handle_request(req, res, next);
    }
  }
};


//layer.js
Layer.prototype.handle_error = function handle_error(error, req, res, next) {
  var fn = this.handle;

  if (fn.length !== 4) {
    // not a standard error handler
    return next(error);
  }

  try {fn(error, req, res, next);
  } catch (err) {next(err);
  }
};

Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;

  if (fn.length > 3) {
    // not a standard request handler
    return next();}

  try {fn(req, res, next);
  } catch (err) {next(err);
  }
};

在 Koa2 中,执行 middleware 的逻辑代码主要位于 koa-compose/index.js 文件:

function compose (middleware) {if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {return Promise.resolve(fn(context, function next () {return dispatch(i + 1)
        }))
      } catch (err) {return Promise.reject(err)
      }
    }
  }
}

由上可知,Express 中 middleware 的 next 参数是一个普通的函数对象,而 Koa 中 middleware 的 next 参数是一个 promise 对象。所以当我们挂载异步的 middleware 时,Express 并不能像 Koa 一样,在 middleware 中使用 await 去等待下一个 middleware 执行完成之后,再执行当前 middleware 的后续逻辑。这就是为什么“Express 是直线型,Koa 是洋葱模型 ”的根本原因。
以上就是我对于 Express 和 Koa 框架的理解,希望对你有帮助。如果上述内容有错误的地方,欢迎大家指正,谢谢~

正文完
 0