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让后面的代码不会运行,但这个方法不好难以控制,当异步操作写在一起的时候,谁知道会是哪一个家伙先结束呢?