nodejs对express中next函数的一些理解


Posted in NodeJs onSeptember 08, 2017

最近公司在使用node做前后端分离,采用的web框架是express,所以对express框架进行了深入的了解,前段时间写了篇关于express路由的文章,但是在那篇文章中貌似少了一个很重要的内容,就是express的next,所以今天单独来说说express的next。

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

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

Next的作用

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

何时使用Next

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

app.get('/a', function(req, res, next) {
  res.send('sucess');
  next();
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
 console.log(404);
 var err = new Error('Not Found');
 err.status = 404;
 next(err);
});

app.use(function(err, req, res, next) {
 res.status(err.status || 500);
 res.render('error', {
  message: err.message,
  error: {}
 });
});

发送请求"/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;

  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;
   }
   ... //此处源码省略
  }
 ... //此处源码省略
  // this should be done for the layer
  if (err) {
    layer.handle_error(err, req, res, next);
  } else {
   layer.handle_request(req, res, next);
  }
 }

上边就是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();
  }

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

这个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,就认为找到了错误处理中间件,然后执行此中间件函数。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
NodeJS制作爬虫全过程
Dec 22 NodeJs
NodeJS学习笔记之(Url,QueryString,Path)模块
Jan 13 NodeJs
nodejs简单实现中英文翻译
May 04 NodeJs
nodejs开发——express路由与中间件
Mar 24 NodeJs
nodejs操作mysql实现增删改查的实例
May 28 NodeJs
nodejs创建简易web服务器与文件读写的实例
Sep 07 NodeJs
Nodejs 和 Electron ubuntu下快速安装过程
May 04 NodeJs
NodeJs生成sitemap站点地图的方法示例
Jun 11 NodeJs
NodeJs 模仿SIP话机注册的方法
Jun 21 NodeJs
nodejs和react实现即时通讯简易聊天室功能
Aug 21 NodeJs
Nodejs技巧之Exceljs表格操作用法示例
Nov 06 NodeJs
NodeJS和浏览器中this关键字的不同之处
Mar 03 NodeJs
nodejs 图解express+supervisor+ejs的用法(推荐)
Sep 08 #NodeJs
nodejs创建简易web服务器与文件读写的实例
Sep 07 #NodeJs
NodeJS设计模式总结【单例模式,适配器模式,装饰模式,观察者模式】
Sep 06 #NodeJs
Nodejs进阶之服务端字符编解码和乱码处理
Sep 04 #NodeJs
Windows下使用Nodejs运行js的方法
Sep 02 #NodeJs
用nodejs实现json和jsonp服务的方法
Aug 25 #NodeJs
NodeJS收发GET和POST请求的示例代码
Aug 25 #NodeJs
You might like
php miniBB中文乱码问题解决方法
2008/11/25 PHP
解析thinkphp的左右值无限分类
2013/06/20 PHP
PHP实现将textarea的值根据回车换行拆分至数组
2015/06/10 PHP
PHP实现根据图片色界在不同位置加水印的方法
2015/08/08 PHP
PHP各种异常和错误的拦截方法及发生致命错误时进行报警
2016/01/19 PHP
CI框架中数据库操作函数$this-&gt;db-&gt;where()相关用法总结
2016/05/17 PHP
Javascript倒计时代码
2010/08/12 Javascript
javascript实现回车键提交表单方法总结
2015/01/10 Javascript
ExpressJS入门实例
2015/01/14 Javascript
简介AngularJS中使用factory和service的方法
2015/06/17 Javascript
详解JavaScript中jQuery和Ajax以及JSONP的联合使用
2015/08/13 Javascript
3种不同的ContextMenu右键菜单实现代码
2016/11/03 Javascript
angular实现表单验证及提交功能
2017/02/01 Javascript
彻底学会Angular.js中的transclusion
2017/03/12 Javascript
Vue2.0父组件与子组件之间的事件发射与接收实例代码
2017/09/19 Javascript
浅谈在Vue-cli里基于axios封装复用请求
2017/11/06 Javascript
Webpack中publicPath路径问题详解
2018/05/03 Javascript
Node.js爬虫如何获取天气和每日问候详解
2019/08/26 Javascript
在layui中layer弹出层点击事件无效的解决方法
2019/09/05 Javascript
vue的滚动条插件实现代码
2019/09/07 Javascript
利用React高阶组件实现一个面包屑导航的示例
2020/08/23 Javascript
python3.6连接MySQL和表的创建与删除实例代码
2017/12/28 Python
Anaconda下配置python+opencv+contribx的实例讲解
2018/08/06 Python
在python中以相同顺序shuffle两个list的方法
2018/12/13 Python
实例详解python函数的对象、函数嵌套、名称空间和作用域
2019/05/31 Python
HTML5、Select下拉框右边加图标的实现代码(增进用户体验)
2017/10/16 HTML / CSS
HTML table 表格边框的实现思路
2019/10/12 HTML / CSS
JD Sports比利时官网:英国领先的运动鞋和运动服饰零售商
2018/10/10 全球购物
Michael Kors英国官网:美国奢侈品品牌
2019/11/13 全球购物
英国户外服装、鞋类和设备的领先零售商:Millets
2020/10/12 全球购物
C++如何引用一个已经定义过的全局变量
2014/08/25 面试题
2014年药店店长工作总结
2014/11/17 职场文书
2015年见习期工作总结
2014/12/12 职场文书
如何写一份成功的商业计划书
2019/06/25 职场文书
Python函数中apply、map、applymap的区别
2021/11/27 Python
JS实现简单的九宫格抽奖
2022/06/28 Javascript