Node程序debug小记 (3)

看到这两行代码,终于可以证实了我们的猜想,因为stream是一个流,接收流的数据需要通过事件传递,而emit就是触发事件所使用的函数。
这也就意味着,resume函数的执行,就代表着stream发送数据的动作,在发送完毕数据后,会执行end,也就是关闭流的操作。

得出结论

到了这里,终于可以得出完整的结论:

formstream在调用field之类的函数后会注册一个微任务
微任务执行时会使用流开始发送数据,数据发送完毕后关闭流
因为在调用urllib之前还注册了一个微任务,导致urllib.request实际上是在这个微任务内部执行的
也就是说在request执行的时候,流已经关闭了,一直拿不到数据,所以就抛出异常,提示接口超时。

那么根据以上的结论,现在就知道该如何修改对应的代码。
在调用field方法之前进行下载图片资源,保证formstream.field与urllib.request之间的代码都是同步的。

let imageUrlResults = yield Promise.all(imageUrlList.map(imgUrl => urllib.request(url) )) const form = new Formstream() form.field('timestamp', moment().unix()) imageUrlResults = imageUrlResults.filter(img => img && img.status === 200).map(img => img.data) imageUrlResults.forEach(imgBuffer => { form.buffer('image', imgBuffer) }) const options = { method: 'POST', headers: form.headers(), stream: form } yield urllib.request(url, options) 小结

这并不是一个有各种高大上名字、方法论的一个调试方式。
不过我个人觉得,它是一个非常有效的方式,而且是一个收获会非常大的调试方式。
因为在调试的过程中,你会去认真的了解你所使用的工具究竟是如何实现的,他们是否真的就像文档中所描述的那样运行。

关于上边这点,顺便吐槽一下这个包:thenify-all。
是一个不错的包,用来将普通的Error-first-callback函数转换为thenalbe函数,但是在涉及到callback会接收多个返回值的时候,该包会将所有的返回值拼接为一个数组并放入resolve中。
实际上这是很令人困惑的一点,因为根据callback返回参数的数量来区别编写代码。
而且thenable约定的规则就是返回callback中的除了error以外的第一个参数。

但是这个在文档中并没有体现,而是简单的使用readFile来举例,很容易对使用者产生误导。
一个最近的例子,就是我使用util.promisify来替换掉thenify-all的时候,发现之前的mysql.query调用莫名其妙的报错了。

// 之前的写法 const [res] = await mysqlClient.query(`SELECT XXX`) // 现在的写法 const res = await mysqlClient.query(`SELECT XXX`)

这是因为在文档中明确定义了,SELECT语句之类的会传递两个参数,第一个是查询的结果集,而第二个是字段的描述信息。
所以thenify-all就将两个参数拼接为了数组进行resolve,而在切换到了官方的实现后,就造成了使用数组解构拿到的只是结果集中的第一条数据。

最后,再简单的总结一下套路,希望能够帮到其他人:

屏蔽异常代码,确定稳定复现(还原修改)

逐步释放,缩小范围(一行行的删除注释)

确定问题,利用基础demo来屏蔽噪音(类似前边的yield Promise.resolve(1)操作)

分析原因,看文档,啃源码(了解这些代码为什么会出错)

通过简单的实验来验证猜想(这时候你就能知道怎样才能避免类似的错误)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgwxsz.html