expressとkoaの比較

51251 ワード

使用体験
koa
const Koa = require('koa');
const app = new Koa();
app.use(ctx => {
   ctx.body = 'Hello Koa';
 });
app.listen(3000);

express
const app = require("express")();
app.use((req,res,next)=>{
    res.status(200).send("

headers ...

"
); })
; app.listen(3001);

注意:この文書はすべてes 6構文で作成されています.環境がサポートされていない場合は、nodeをアップグレードするか、babelを使用してトランスコードしてください.
きどうモード
  • koaはnew Koa()の方式を採用し、expressは伝統的な関数形式を採用し、ソースコードを比較すると以下の通りである:
  • //koa
    const Emitter = require('events');
    module.exports = class Application extends Emitter {
    ...
    }
    //express
    exports = module.exports = createApplication;
    function createApplication() {
    ...
    }

    見えるkoa@2es 6の構文実装を採用し、Emitterクラスを継承しており、具体的な情報はEmitterの説明を参照することができる.これはkoa@2es 6以上の環境でのみ実行でき、低バージョンでは使用を考慮できます[email protected].一方expressは伝統的でfunctionの形式でエクスポートされています.2.中間部品の形式は両者が異なり、これは両者が中間部品を処理する論理の違いによるもので、実際にはこれも両者の最も根本的な違いであり、具体的な分析は後に残して比較し、ここでは主に両者の使用上の違いを比較し、以下に示す.
    expressは複数のミドルウェアを処理する
    const app = require("express")();
    app.use((req,res,next)=>{
        console.log("first");
        //next();
    });
    app.use((req,res,next)=>{
        console.log("second");
        //next();
    });
    app.use((req,res,next)=>{
        console.log("third");
        res.status(200).send("

    headers ...

    "
    ); })
    ; app.listen(3001);

    このように書くと、端末はfirstしか印刷されず、後悔せず、フロントエンドリクエストはタイムアウトまで待機します.この問題の原因は、expressがnext()をアクティブに呼び出して中間価格を継続させ、注釈を放す必要があるためです.これにより、リクエストにどのように応答するかを自主的に制御することができます.
    koaは複数のミドルウェアを処理する
    const Koa = require('koa');
    const app = new Koa();
    app.use((ctx,next) => {
       ctx.body = 'Hello Koa-1';
       next();
     });
     app.use((ctx,next) => {
       ctx.body = 'Hello Koa-2';
       next();
     });
     app.use((ctx,next) => {
       ctx.body = 'Hello Koa-3';
       next();
     });
    app.listen(3000);

    expressと同様にkoaミドルウェアのパラメータも2つあり,後者がnextである.nextの機能はexpressと同様であり,ここでは後述しない.
    上でkoaのnext()の機能を紹介しました.ここのnext()は同期呼び出しが必要です.決して非同期呼び出しを採用しないでください.次の形式に書かないでください.これは未呼び出しnext()に相当します.具体的な原因は、後のソースコード部分で分析されます.
        app.use((ctx,next) => {
       ctx.body = 'Hello Koa-2';
       setTimeout(()=>next(),3000);
       //next();
     });

    両方の使用ロジックが異なることを上述したが、koaはパラメータにcontextを与え、この構造体には要求を返すすべての情報が含まれているため、次のコードを書くことができる.
    const Koa = require('koa');
    const app = new Koa();
    
    app.use((ctx)=>{
        const res = ctx.res;
        res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8','Accept-Language':'zh-CN,zh;q=0.8,en;q=0.6'});
        res.end('

    '
    ); })
    ; // response app.use(ctx => { ctx.body = 'Hello Koa'; }); app.listen(3000);

    このような論理はexpressとよく似ていて、原理も同じです.このように書くと、フロントエンドの要求が得られた結果は であり、後続のapp.useは実際に実行されなかった.
    expressルーティング処理
    expressのコードは一般的に以下の通りです.
    const app = require("express")();
    app.use("/first",(req,res,next)=>{
        console.log("first");
        res.status(200).send("

    headers-first ...

    "
    ); })
    ; app.use("/second",(req,res,next)=>{ console.log("second"); res.status(200).send("

    headers-second ...

    "
    ); })
    ; app.use("/third",(req,res,next)=>{ console.log("third"); res.status(200).send("

    headers-third ...

    "
    ); })
    ; app.listen(3001);

    これはよく理解できますが、リクエストパスによって異なる結果を返します.koaは?
    koa分路処理
    const Koa = require('koa');
    const app = new Koa();
    app.use("/",ctx => {
       ctx.body = 'Hello Koa';
     });
    app.listen(3000);

    koa自体がルーティング対応をサポートしていないため、必要に応じてサードパーティ製パッケージを導入することで実現できます.koajsには簡単なrouterパッケージがあります.具体的な書き方は以下の通りです.
    //   Koa Trie Router
    const Koa = require('koa')
    const Router = require('koa-trie-router')
    
    let app = new Koa()
    let router = new Router()
    
    router
      .use(function(ctx, next) {
        console.log('* requests')
        next()
      })
      .get(function(ctx, next) {
        console.log('GET requests')
        next()
      })
      .put('/foo', function (ctx) {
        ctx.body = 'PUT /foo requests'
      })
      .post('/bar', function (ctx) {
        ctx.body = 'POST /bar requests'
      })
    
    app.use(router.middleware())
    app.listen(3000)

    具体的なアプリケーションでは他のルーティングパケットを用いて行うこともでき,githubでは多くのことが検索できる.また,実装の理由から,印刷要求処理に時間がかかるという興味深い現象を紹介する.
    koaバージョン
    const Koa = require('koa');
    const app = new Koa();
    app.use((ctx,next) => {
       ctx.body = 'Hello Koa-1';
       let start = new Date();
       next().then(()=>{
            console.log("time cost:",new Date()-start);
       });
     });
     app.use((ctx,next) => {
       ctx.body = 'Hello Koa-2';
       next();
     });
     app.use((ctx,next) => {
       ctx.body = 'Hello Koa-3';
       next();
     });
    app.listen(3000);

    koaはpromise方式でミドルウェアを処理しているため、next()は実際にpromiseオブジェクトを返しているので、上記の簡単な方式で処理時間を記録することができる.es 7では、より簡単な書き方ができます.
    const Koa = require('koa');
    const app = new Koa();
    app.use(async (ctx,next) => {
       ctx.body = 'Hello Koa-1';
       let start = new Date();
       await next();
       console.log("time cost:",new Date()-start);
     });
     app.use(async (ctx,next) => {
       ctx.body = 'Hello Koa-2';
       //                  
       await new Promise((resolve,reject)=>setTimeout(()=>{next();resolve();},3000));
     });
     app.use((ctx,next) => {
       ctx.body = 'Hello Koa-3';
       next();
     });
    app.listen(3000);

    これにより、入口にミドルウェアを1つ置くだけで時間のかかる記録が完了します.
    expressバージョン
    expressはpromiseではなくコールバック方式でミドルウェアを処理しているため,上記のような便利な方式では時間のかかる取得はできない.next()をカプセル化しても、後続のnext()がすべてカプセル化されてこそ正しい結果が得られることを保証しなければならないため、何の役にも立たない.次に、リファレンスインプリメンテーションを示します.
    let time = null;
    .use('/', (req, res, next) => {
          time = Date.now();
          next()
        })
        .use('/eg', bidRequest)
        .use('/', (req, res, next) => {
          console.log(`<= time cost[${req.baseUrl}] : `, Date.now() - time, 'ms');
        })

    まとめ
    koaとexpressの違いはやはり比較的に大きくて、koaの内容は少なくて、nodejs自身のcreateServer関数に対して簡単なパッケージをして、多くの延長をしていません;expressは主にkoaよりrouterが多い.両者のコードの考え方はやはり違いますが、実際の使用には大きな障害はありません.
    ソース分析
    koa
    koaのソースコードには主に4つのファイルがあります:application.js,context.js,request.js,response.js
    context.js
    contextには実際の機能コードはありません.いくつかの基礎関数と変数だけで、次はコードクリップです.
    inspect() {
        return this.toJSON();
      },
      toJSON() {
        return {
          request: this.request.toJSON(),
          response: this.response.toJSON(),
          app: this.app.toJSON(),
          originalUrl: this.originalUrl,
          req: '',
          res: '',
          socket: ''
        };
      },

    request.js
    このファイルには主にset関数とget関数が山積みされており、主に要求構造体の特定のフィールドを取得したり、次のipを取得する関数などの特定のフィールドを変更したりするために使用されています.コードはよく理解されています.
    get ips() {
        const proxy = this.app.proxy;
        const val = this.get('X-Forwarded-For');
        return proxy && val
          ? val.split(/\s*,\s*/)
          : [];
      },

    response.js
    responseはrequestに対応し、主にresを処理するツールクラスであり、以下はresのcontent-lengthを設定および取得するためのコードクリップである.
    set length(n) {
        this.set('Content-Length', n);
      },
      get length() {
        const len = this.header['content-length'];
        const body = this.body;
    
        if (null == len) {
          if (!body) return;
          if ('string' == typeof body) return Buffer.byteLength(body);
          if (Buffer.isBuffer(body)) return body.length;
          if (isJSON(body)) return Buffer.byteLength(JSON.stringify(body));
          return;
        }
    
        return ~~len;
      },

    このうち~~lenが使われていますが、これはちょっと面白いです.2回反転して、出力が数字であることを保証することができます.lenが文字列であれば0を返します.(初めて見た…)
    application.js
    上記で使用したappは、このファイルで定義され、koaの核心でもあります.ここでは、いくつかのメンバー関数を選択して分析します(ファイルコード全体が250行未満で、自分で読む圧力も大きくありません).
    module.exports = class Application extends Emitter {
    /*
            : req,res,env           context,           context  ,          req res 。
    */
      constructor() {
        super();
        this.proxy = false;
        this.middleware = [];
        this.subdomainOffset = 2;
        this.env = process.env.NODE_ENV || 'development';
        this.context = Object.create(context);
        this.request = Object.create(request);
        this.response = Object.create(response);
      }
    /*
               nodejs   createServer,      。
    */
      listen() {
        debug('listen');
        const server = http.createServer(this.callback());
        return server.listen.apply(server, arguments);
      }
      //    
      use(fn) {
        if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
        if (isGeneratorFunction(fn)) {
          deprecate('Support for generators will be removed in v3. ' +
                    'See the documentation for examples of how to convert old middleware ' +
                    'https://github.com/koajs/koa/blob/master/docs/migration.md');
          fn = convert(fn);
        }
        debug('use %s', fn._name || fn.name || '-');
        this.middleware.push(fn);
        return this;
      }
      //    
      callback() {
        const fn = compose(this.middleware);
    
        if (!this.listeners('error').length) this.on('error', this.onerror);
    
        const handleRequest = (req, res) => {
          res.statusCode = 404;
          const ctx = this.createContext(req, res);
          const onerror = err => ctx.onerror(err);
          const handleResponse = () => respond(ctx);
          onFinished(res, onerror);
          return fn(ctx).then(handleResponse).catch(onerror);
        };
    
        return handleRequest;
      }
    
      //  context
      createContext(req, res) {
        const context = Object.create(this.context);
        const request = context.request = Object.create(this.request);
        const response = context.response = Object.create(this.response);
        context.app = request.app = response.app = this;
        context.req = request.req = response.req = req;
        context.res = request.res = response.res = res;
        request.ctx = response.ctx = context;
        request.response = response;
        response.request = request;
        context.originalUrl = request.originalUrl = req.url;
        context.cookies = new Cookies(req, res, {
          keys: this.keys,
          secure: request.secure
        });
        request.ip = request.ips[0] || req.socket.remoteAddress || '';
        context.accept = request.accept = accepts(req);
        context.state = {};
        return context;
      }
    //    
    function respond(ctx) {
      // allow bypassing koa
      if (false === ctx.respond) return;
    
      const res = ctx.res;
      if (!ctx.writable) return;
    
      let body = ctx.body;
      const code = ctx.status;
    
      // ignore body
      if (statuses.empty[code]) {
        // strip headers
        ctx.body = null;
        return res.end();
      }
    
      if ('HEAD' == ctx.method) {
        if (!res.headersSent && isJSON(body)) {
          ctx.length = Buffer.byteLength(JSON.stringify(body));
        }
        return res.end();
      }
    
      // status body
      if (null == body) {
        body = ctx.message || String(code);
        if (!res.headersSent) {
          ctx.type = 'text';
          ctx.length = Buffer.byteLength(body);
        }
        return res.end(body);
      }
    
      // responses
      if (Buffer.isBuffer(body)) return res.end(body);
      if ('string' == typeof body) return res.end(body);
      if (body instanceof Stream) return body.pipe(res);
    
      // body: json
      body = JSON.stringify(body);
      if (!res.headersSent) {
        ctx.length = Buffer.byteLength(body);
      }
      res.end(body);
    }

    次にuse,callback,respondの3つの関数に重点を置き,実際にkoaの情報ストリームを理解してこの3つの関数のソースコードを見るだけで十分ではない.use:内容は多くありません.そのうち、1つ目のifはセキュリティチェックに使用され、2つ目のifはgenerator関数の互換性を実現するために使用されます.具体的な実現過程はis-generator-functionというパッケージの中にあります.興味があれば見てもいいですが、やはりテクニックがあります.参考にしてください.useは最終的に中間部品pushthis.middleware配列に入れただけで、実質的な論理操作はありません.respond:この関数はリクエストに応答する場所です.これも、リクエストにアクティブに応答しなくてもいい理由です.関数には多くの判断がなされており,主に二次応答や特殊な特定の応答の要求を防止する.callback:callbackは、createServer関数のコールバック、すなわちhandleRequest関数を生成するために使用される.handleRequestの戻り値はpromiseオブジェクトです.ここでは、中間部品配列を関数に変換して、使いやすいようにするcomposeメソッドが呼び出されていることに注意してください.具体的な実装はkoa-composeというパッケージにあり、ここではその一部を抜粋して分析する.
    //   compose(...)     
    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)
          }
        }
      }

    実際には,ここでは閉パケットを用いてミドルウェア配列の遍歴を実現していることがわかる.具体的には,i+1個目のミドルウェアをnextとしてi個目のミドルウェアに伝達することが考えられるが,これは,nextをアクティブに呼び出さなければならない理由であり,nextをアクティブに呼び出さなければループが早期に終了し,後続のミドルウェアが実行されないと考えられる.
    これまでkoaソース分析は終わり,koaソースコードは少なく,余分なものはなく,ルーティングさえ他のパケットを導入する必要があった.
    express
    expressのソースコードはkoaより多くのものがあり、ここではコア部分だけを比較し、他の部分の内容を無視します.
    //express.js
    function createApplication() {
      var app = function(req, res, next) {
        app.handle(req, res, next);
      };
    
      mixin(app, EventEmitter.prototype, false);
      mixin(app, proto, false);
    
      // expose the prototype that will get set on requests
      app.request = Object.create(req, {
        app: { configurable: true, enumerable: true, writable: true, value: app }
      })
    
      // expose the prototype that will get set on responses
      app.response = Object.create(res, {
        app: { configurable: true, enumerable: true, writable: true, value: app }
      })
    
      app.init();
      return app;
    }

    ここでのexpressは私たちが上述したexpressオブジェクトであり、実際にこの関数はいくつかの一般的な機能と変数をappオブジェクトにバインドしており、私たちがコードで使用しているapp.eg_funcsのような方法はここから継承されていることがわかります.実際には、オブジェクトは、app.listen()を使用してサービスを開始することに限定されず、以下はlisten関数のコードである.
    //application.js
    app.listen = function listen() {
      var server = http.createServer(this);
      return server.listen.apply(server, arguments);
    };

    呼び出しapp.listenは、サーバを起動することができます.実際には、この2つのコードを直接手動で書いてサービスを起動することもできます.socket.ioおよびhttpsサービスでは、このプロセスを自分で完了する必要があります.次はapp.useのソースです.
    app.use = function use(fn) {
      var offset = 0;
      var path = '/';
    
      // default path to '/'
      // disambiguate app.use([fn])
      if (typeof fn !== 'function') {
        var arg = fn;
    
        while (Array.isArray(arg) && arg.length !== 0) {
          arg = arg[0];
        }
    
        // first arg is the path
        if (typeof arg !== 'function') {
          offset = 1;
          path = fn;
        }
      }
    
      var fns = flatten(slice.call(arguments, offset));
    
      if (fns.length === 0) {
        throw new TypeError('app.use() requires middleware functions');
      }
    
      // setup router
      this.lazyrouter();
      var router = this._router;
    
      fns.forEach(function (fn) {
        // non-express app
        if (!fn || !fn.handle || !fn.set) {
          return router.use(path, fn);
        }
    
        debug('.use app under %s', path);
        fn.mountpath = path;
        fn.parent = this;
    
        // restore .app property on req and res
        router.use(path, function mounted_app(req, res, next) {
          var orig = req.app;
          fn.handle(req, res, function (err) {
            setPrototypeOf(req, orig.request)
            setPrototypeOf(res, orig.response)
            next(err);
          });
        });
    
        // mounted an app
        fn.emit('mount', this);
      }, this);
    
      return this;
    }

    ここには、最初のパラメータがパスであれば関数であれば、パラメータを判断する論理がありますが、普段はあまり書かれていません.
    実際にexpressはrouterを呼び出したuseメソッドがミドルウェアを処理していることがわかります.router.useは/router/index.jsで定義され、ソースコードは以下の通りです.
    proto.use = function use(fn) {
      var offset = 0;
      var path = '/';
    
      // default path to '/'
      // disambiguate router.use([fn])
      if (typeof fn !== 'function') {
        var arg = fn;
    
        while (Array.isArray(arg) && arg.length !== 0) {
          arg = arg[0];
        }
    
        // first arg is the path
        if (typeof arg !== 'function') {
          offset = 1;
          path = fn;
        }
      }
    
      var callbacks = flatten(slice.call(arguments, offset));
    
      if (callbacks.length === 0) {
        throw new TypeError('Router.use() requires middleware functions');
      }
    
      for (var i = 0; i < callbacks.length; i++) {
        var fn = callbacks[i];
    
        if (typeof fn !== 'function') {
          throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
        }
    
        // add the middleware
        debug('use %o %s', path, fn.name || '')
    
        var layer = new Layer(path, {
          sensitive: this.caseSensitive,
          strict: false,
          end: false
        }, fn);
    
        layer.route = undefined;
    
        this.stack.push(layer);
      }
    
      return this;
    };

    その中の前の大半は主にいくつかの準備作業です(この書き方はexpressでよく見られるようです).後でkoaが直接ミドルウェアを配列にpushするのとは異なり、expressはミドルウェアをLayerにカプセル化し、これもミドルウェアの実行をよりよく制御するためである.Layerのコードは/router/layer.jsにあります.(ここでは分析しない)
    次にexpressがリクエストにどのように応答するかを分析します.上のlisten部分のコードから、createServerにthisを送信しました.このthisはexpress()の戻り値で、application.jsに定義されています.ソースコードは以下の通りです.
    var app = function(req, res, next) {
        app.handle(req, res, next);
      };

    実際にappはhandleメソッドを呼び出し、このメソッドはアプリケーションオブジェクトから継承され、アプリケーション.jsを表示すると次のコードが見つかります.
    //    this._router
    this._router = new Router({
          caseSensitive: this.enabled('case sensitive routing'),
          strict: this.enabled('strict routing')
        });
    
        this._router.use(query(this.get('query parser fn')));
        this._router.use(middleware.init(this));
    //   this._router
    app.handle = function handle(req, res, callback) {
      var router = this._router;
    
      // final handler
      var done = callback || finalhandler(req, res, {
        env: this.get('env'),
        onerror: logerror.bind(this)
      });
    
      // no routes
      if (!router) {
        debug('no routes defined on app');
        done();
        return;
      }
    
      router.handle(req, res, done);
    };

    実際にここで呼び出されたのはrouter.handleで、routerのソースコードを見てみましょう.
    proto.handle = function handle(req, res, out) {
      var self = this;
    
      debug('dispatching %s %s', req.method, req.url);
    
      var idx = 0;
      var protohost = getProtohost(req.url) || ''
      var removed = '';
      var slashAdded = false;
      var paramcalled = {};
    
      // store options for OPTIONS request
      // only used if OPTIONS request
      var options = [];
    
      // middleware and routes
      var stack = self.stack;
    
      // manage inter-router variables
      var parentParams = req.params;
      var parentUrl = req.baseUrl || '';
      var done = restore(out, req, 'baseUrl', 'next', 'params');
    
      // setup next layer
      req.next = next;
    
      // for options requests, respond with a default if nothing else responds
      if (req.method === 'OPTIONS') {
        done = wrap(done, function(old, err) {
          if (err || options.length === 0) return old(err);
          sendOptionsResponse(res, options, old);
        });
      }
    
      // setup basic req values
      req.baseUrl = parentUrl;
      req.originalUrl = req.originalUrl || req.url;
    
      next();
    
      function next(err) {
        var layerError = err === 'route'
          ? null
          : err;
    
        // remove added slash
        if (slashAdded) {
          req.url = req.url.substr(1);
          slashAdded = false;
        }
    
        // restore altered req.url
        if (removed.length !== 0) {
          req.baseUrl = parentUrl;
          req.url = protohost + removed + req.url.substr(protohost.length);
          removed = '';
        }
    
        // signal to exit router
        if (layerError === 'router') {
          setImmediate(done, null)
          return
        }
    
        // no more matching layers
        if (idx >= stack.length) {
          setImmediate(done, layerError);
          return;
        }
    
        // get pathname of request
        var path = getPathname(req);
    
        if (path == null) {
          return done(layerError);
        }
    
        // find next matching layer
        var layer;
        var match;
        var route;
    
        while (match !== true && idx < stack.length) {
          layer = stack[idx++];
          match = matchLayer(layer, path);
          route = layer.route;
    
          if (typeof match !== 'boolean') {
            // hold on to layerError
            layerError = layerError || match;
          }
    
          if (match !== true) {
            continue;
          }
    
          if (!route) {
            // process non-route handlers normally
            continue;
          }
    
          if (layerError) {
            // routes do not match with a pending error
            match = false;
            continue;
          }
    
          var method = req.method;
          var has_method = route._handles_method(method);
    
          // build up automatic options response
          if (!has_method && method === 'OPTIONS') {
            appendMethods(options, route._options());
          }
    
          // don't even bother matching route
          if (!has_method && method !== 'HEAD') {
            match = false;
            continue;
          }
        }
    
        // no match
        if (match !== true) {
          return done(layerError);
        }
    
        // store route for dispatch on change
        if (route) {
          req.route = route;
        }
    
        // Capture one-time layer values
        req.params = self.mergeParams
          ? mergeParams(layer.params, parentParams)
          : layer.params;
        var layerPath = layer.path;
    
        // this should be done for the layer
        self.process_params(layer, paramcalled, req, res, function (err) {
          if (err) {
            return next(layerError || err);
          }
    
          if (route) {
            return layer.handle_request(req, res, next);
          }
    
          trim_prefix(layer, layerError, layerPath, path);
        });
      }
    
      function trim_prefix(layer, layerError, layerPath, path) {
        if (layerPath.length !== 0) {
          // Validate path breaks on a path separator
          var c = path[layerPath.length]
          if (c && c !== '/' && c !== '.') return next(layerError)
    
          // Trim off the part of the url that matches the route
          // middleware (.use stuff) needs to have the path stripped
          debug('trim prefix (%s) from url %s', layerPath, req.url);
          removed = layerPath;
          req.url = protohost + req.url.substr(protohost.length + removed.length);
    
          // Ensure leading slash
          if (!protohost && req.url[0] !== '/') {
            req.url = '/' + req.url;
            slashAdded = true;
          }
    
          // Setup base URL (no trailing slash)
          req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
            ? removed.substring(0, removed.length - 1)
            : removed);
        }
    
        debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
    
        if (layerError) {
          layer.handle_error(layerError, req, res, next);
        } else {
          layer.handle_request(req, res, next);
        }
      }
    };

    この関数は長いが,ほとんどの内容がマッチングルーティング,タイプ検出などの操作であり,実際の操作はnext()関数に集中しており,koaと同様に,ここでも閉パケットを用いてミドルウェア配列をループする.next()の実行部分を見ると、通常、実際の操作はlayer.handle_requestによって行われています.次に、layer.jsのソースコードを見ます.
    //   
    function Layer(path, options, fn) {
      if (!(this instanceof Layer)) {
        return new Layer(path, options, fn);
      }
    
      debug('new %o', path)
      var opts = options || {};
    
      this.handle = fn;
      this.name = fn.name || '';
      this.params = undefined;
      this.path = undefined;
      this.regexp = pathRegexp(path, this.keys = [], opts);
    
      // set fast path flags
      this.regexp.fast_star = path === '*'
      this.regexp.fast_slash = path === '/' && opts.end === false
    }
    //    
    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);
      }
    };

    bingo,ここではuseを呼び出すときに初期化されたLayer構造体であり,実際にはlayer.handleにミドルウェア関数を付与しているが,実際の処理関数handle_requestではthis.handleを呼び出す.やっとデータ処理の根源が见つかりました....ここで実际にはrouternext()はミドルウェアコールバックのプロセスを开始しただけで、次のミドルウェアに自分を伝え、后のミドルウェアはnext()をアクティブに呼び出すことで伝えることができます.
    ミドルウェアを処理する論理的にexpressは、1つのミドルウェアが実行されるたびに、次のミドルウェアを起動するために「センター」に自発的に通知することを理解することができる.koaはチェーンプロセスとして理解でき、各ミドルウェアは後のミドルウェアを起動する.
    ここまで、koaとexpressの比較分析を基本的に完成し、両者にはそれぞれ独自の特徴があり、使用中にニーズに応じて最適なソリューションを選択することができます.