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
的继续执行,如果第一个Promise
的res.send
已经执行,当第二个Promise
被执行时,执行至第三步,也就是设置响应头的时候原先的响应头信息依然存在,触犯了**node
**中不能重复设置响应头信息的规定,所以node
先抛错,这一切看起来也就顺其自然了
All in all 解决方案呢?
- 避免使用多次
send
,多次设置响应头就会出现此错误 - 谨慎使用异步
Promise
,可以考虑Promise
嵌套**(推荐)** send
之前记得加上return
让后面的代码不会运行,但这个方法不好难以控制,当异步操作写在一起的时候,谁知道会是哪一个家伙先结束呢?