对express中next函数的一些理解

关于next主要从三点来进行说明:

  1. next的作用是什么?
  2. 我们应该在何时使用next?
  3. next的内部实现机制是什么?

Next的作用

我们在定义express中间件函数的时候都会将第三个参数定义为next,这个next就是我们今天的主角,next函数主要负责将控制权交给下一个中间件,如果当前中间件没有终结请求,并且next没有被调用,那么请求将被挂起,后边定义的中间件将得不到被执行的机会。

何时使用Next

从上边的描述我们已经知道,next函数主要是用来确保所有注册的中间件被一个接一个的执行,那么我们就应该在所有的中间件中调用next函数,但有一个特例,如果我们定义的中间件终结了本次请求,那就不应该再调用next函数,否则就可能会出问题,我们来看段代码

发送请求"/a",控制台打印日志如下:

404
GET /a 500 6.837 ms - -
Error: Can't set headers after they are sent.
    at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:345:11)

为什么代码会抛异常呢,就是因为我们在res.send之后调用了next函数,虽然我们本次的请求已经被终止,但后边的404中间件依旧会被执行,而后边的中间件试图去向res的headers中添加属性值,所以就会抛出上边的异常。

读到这你可能会有个疑问,如果我不在res.send后边调用next函数,那后边定义的404中间件是不是永远都不会被执行到。现在我们删除res.send后边next函数调用,发送请求"/xxx",我们就会发现404中间件被执行了,(ㄒoㄒ),这不是和我们之前说的矛盾了吗,我们的自定义中间件没有调用next,但后边定义的中间件仍旧被执行了,这究竟是为什么呢。看来只能求助源码了~~~

Next的内部机制

function next(err) {
    ... //此处源码省略
    // find next matching layer
    var layer;
    var match;
    var route;
</span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">match </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> idx </span><span class="pun">&lt;</span><span class="pln"> stack</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  layer </span><span class="pun">=</span><span class="pln"> stack</span><span class="pun">[</span><span class="pln">idx</span><span class="pun">++];</span><span class="pln">
  match </span><span class="pun">=</span><span class="pln"> matchLayer</span><span class="pun">(</span><span class="pln">layer</span><span class="pun">,</span><span class="pln"> path</span><span class="pun">);</span><span class="pln">
  route </span><span class="pun">=</span><span class="pln"> layer</span><span class="pun">.</span><span class="pln">route</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">typeof</span><span class="pln"> match </span><span class="pun">!==</span><span class="pln"> </span><span class="str">'boolean'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// hold on to layerError</span><span class="pln">
    layerError </span><span class="pun">=</span><span class="pln"> layerError </span><span class="pun">||</span><span class="pln"> match</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">match </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">...</span><span class="pln"> </span><span class="com">//此处源码省略</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span><span class="pln"> </span><span class="com">//此处源码省略</span><span class="pln">
</span><span class="com">// this should be done for the layer</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    layer</span><span class="pun">.</span><span class="pln">handle_error</span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">next</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    layer</span><span class="pun">.</span><span class="pln">handle_request</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">next</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

}

上边就是express中next的源码,为了更容易说明问题,对代码进行了删减。从上边的源码可以发现,next函数内部有个while循环,每次循环都会从stack中拿出一个layer,这个layer中包含了路由和中间件信息,然后就会用layer和请求的path就行匹配,如果匹配成功就会执行layer.handle_request,调用中间件函数。但如果匹配失败,就会循环下一个layer(即中间件)。

现在我们就能解释上边提出的问题了,为什么我们的自定义中间件中没调用next函数,但后边的404中间件仍旧会被执行到,因为我们请求的"/xxx"匹配不到我们注册的"/a"路由中间件,所以while循环会继续往下执行,匹配404中间件成功,所以会执行404中间件。

注意:app.use注册的中间件,如果path参数为空,则默认为"/",而path为"/"的中间件默认匹配所有的请求。

有一点需要特别指出,其实我们在定义路由中间件的时候函数的第三个参数next和我们定义非路由中间件的函数的第三个参数next不是同一个next,我们在上边看到的是非路由中间件的next,而路由中间件的next函数是这样的

function next(err) {
    if (err && err === 'route') {
      return done();
    }
</span><span class="kwd">var</span><span class="pln"> layer </span><span class="pun">=</span><span class="pln"> stack</span><span class="pun">[</span><span class="pln">idx</span><span class="pun">++];</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">layer</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">done</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">layer</span><span class="pun">.</span><span class="pln">method </span><span class="pun">&amp;&amp;</span><span class="pln"> layer</span><span class="pun">.</span><span class="pln">method </span><span class="pun">!==</span><span class="pln"> method</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">next</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  layer</span><span class="pun">.</span><span class="pln">handle_error</span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">next</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  layer</span><span class="pun">.</span><span class="pln">handle_request</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">next</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

}

这个next比上边的那个next要简单很多,它负责同一个路由的多个中间件的控制权的传递,并且它会接收一个参数"route",如果调用next(“route”),则会跳过当前路由的其它中间件,直接将控制权交给下一个路由。

最后有必要再说一说next(err),next(err)是如何将控制权传递到错误处理中间件的,从前边的代码我们知道,当调用next(err)是,express内部会调用layer.handle_error,那我们来看看它的源码

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);
}
};

代码中的fn就是中间件函数,express会对fn的参数个数进行判断,如果参数个数不等于4则认为不是错误处理中间件,则继续调用next(err),这样就会进入到下一个中间件函数,继续进行参数个数判断,如此方式一直到某个中间件函数的参数个数是4,就认为找到了错误处理中间件,然后执行此中间件函数。

猜你喜欢

转载自blog.csdn.net/qq_38169981/article/details/88886902