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 相关文章推荐
golang、python、php、c++、c、java、Nodejs性能对比
Mar 12 NodeJs
基于NodeJS的前后端分离的思考与实践(五)多终端适配
Sep 26 NodeJs
轻松创建nodejs服务器(3):代码模块化
Dec 18 NodeJs
轻松创建nodejs服务器(10):处理POST请求
Dec 18 NodeJs
NodeJS中Buffer模块详解
Jan 07 NodeJs
使用nodejs下载风景壁纸
Feb 05 NodeJs
nodejs中使用HTTP分块响应和定时器示例代码
Mar 19 NodeJs
详解nodejs微信jssdk后端接口
May 25 NodeJs
nodejs基于express实现文件上传的方法
Mar 19 NodeJs
详解NodeJS Https HSM双向认证实现
Mar 12 NodeJs
nodejs中使用worker_threads来创建新的线程的方法
Jan 22 NodeJs
ubuntu系统下使用pm2设置nodejs开机自启动的方法
May 12 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图片加中文水印实现代码分享
2012/10/31 PHP
PHP 使用header函数设置HTTP头的示例解析 表头
2013/06/17 PHP
PHP动态输出JavaScript代码实例
2015/02/12 PHP
php实现背景图上添加圆形logo图标的方法
2016/11/17 PHP
Windows下wamp php单元测试工具PHPUnit安装及生成日志文件配置方法
2018/05/28 PHP
WEB页子窗口(showModalDialog和showModelessDialog)使用说明
2009/10/25 Javascript
javascript 最常用的10个自定义函数[推荐]
2009/12/26 Javascript
检测input每次的输入是否合法遇到汉字输入就有问题
2012/05/23 Javascript
JS实现霓虹灯文字效果的方法
2015/08/06 Javascript
JavaScript缓冲运动实现方法(2则示例)
2016/01/08 Javascript
jQuery实现漂亮实用的商品图片tips提示框效果(无图片箭头+阴影)
2016/04/16 Javascript
jQuery中的insertBefore(),insertAfter(),after(),before()区别介绍
2016/09/01 Javascript
JavaScript之WebSocket技术详解
2016/11/18 Javascript
微信小程序 input输入框详解及简单实例
2017/01/10 Javascript
js使用i18n实现页面国际化的方法
2017/05/09 Javascript
你点的 ES6一些小技巧,请查收
2018/04/25 Javascript
详解用vue2.x版本+adminLTE开源框架搭建后台应用模版
2019/03/15 Javascript
JS判断数组里是否有重复元素的方法小结
2019/05/21 Javascript
浅谈js中的attributes和Attribute的用法与区别
2020/07/16 Javascript
如何利用JS将手机号中间四位变成*号
2020/09/29 Javascript
Python FTP操作类代码分享
2014/05/13 Python
利用Python中的mock库对Python代码进行模拟测试
2015/04/16 Python
python的else子句使用指南
2016/02/27 Python
Django Highcharts制作图表
2016/08/27 Python
Python数据拟合与广义线性回归算法学习
2017/12/22 Python
Python爬虫实现全国失信被执行人名单查询功能示例
2018/05/03 Python
使用jupyter notebook将文件保存为Markdown,HTML等文件格式
2020/04/14 Python
Python函数递归调用实现原理实例解析
2020/08/11 Python
美国大码时尚女装购物网站:ELOQUII
2017/12/28 全球购物
CheapTickets泰国:廉价航班,查看促销价格并预订机票
2019/12/28 全球购物
电子邮箱格式怎么写
2014/01/12 职场文书
致共产党员倡议书
2014/04/16 职场文书
我的中国梦演讲稿600字
2014/08/19 职场文书
2014年人事工作总结范文
2014/11/19 职场文书
2014年乡镇纪委工作总结
2014/12/19 职场文书
教你如何让spark sql写mysql的时候支持update操作
2022/02/15 MySQL