Express下多次callback引发的问题

目录

错误结果

Error: Can’t render headers after they are sent to the client.

错误:当响应头被发回客户端时不能被正确渲染!

出现场景

Express环境下使用mongoose model 进行模糊查询时,需要用ORM进行至少一次至多两次的查询,查询用的都是Promise进行的异步操作

问题排查

  • 响应头的设置出现了冲突

  • res.send(docs)出错

  • 调试时移除了其中一个Promise查询,错误消失

  • 问题出在Promise

问题分析

为什么连续的两个Promise回调会最终导致响应头渲染冲突?

渲染冲突出现在错误的顶部,跟错误抛出的顺序有关,这与Express自带的res对象有关,而res又继承自node.js原生的http.ServerResponse 类,在res调用res.writeHead(statusCode)写入响应头的状态码之前,我们可以尽情的写入响应头的头信息,在一个res.send(docs)中,应该包含着以下几步,有的是exprerss自己的,有的则是属于node原生http模块的

按以下顺序开始运行:

res.writeContinue()

res.statusCode = 404

res.setHeader(name, value)

res.getHeader(name)

res.removeHeader(name)

res.header(key[, val]) (Express only)

res.charset = ‘utf-8’ (Express only)

res.contentType(type) (Express only)

res.send([body]) (Express only)

最后我们的查询数据是以响应内容中的body的形式返回给客户端的,当要进行send的时候,node自带的函数就会先运行,当然运行第一个Promise的时候是没有问题的,问题在于:send本身并不能中断当前执行的任务,只要还没有return,代码仍然会继续运行,这也恰恰印证了node的异步非阻塞IO的特性,即便是第一个Promise执行时,并不会阻塞第二个Promise的继续执行,如果第一个Promiseres.send已经执行,当第二个Promise被执行时,执行至第三步,也就是设置响应头的时候原先的响应头信息依然存在,触犯了**node**中不能重复设置响应头信息的规定,所以node先抛错,这一切看起来也就顺其自然了

All in all 解决方案呢?

  • 避免使用多次send,多次设置响应头就会出现此错误
  • 谨慎使用异步Promise,可以考虑Promise嵌套**(推荐)**
  • send之前记得加上return让后面的代码不会运行,但这个方法不好难以控制,当异步操作写在一起的时候,谁知道会是哪一个家伙先结束呢?