前段时间为了参加校内的一个比赛,做了个识别东西的小程序,其实就是API的搬运工,把各种识别的API集合在一起。
API的调用是在云函数内,虽然早就听说,但是真正的用却是第一次。
我最开始写的代码如下:
let req = https.request(options, function (res) {
res.on('data', (data) => {
resolve(JSON.parse(decoder.write(finalData)));
});
res.on('error', (err)=>{
reject(err);
});
});
req.write(postData);
req.end();
可能有经验的开发者一眼就能看出这里有什么问题,可是对于我这样的新手来说写出这样的代码还丝毫没有察觉到危险的逼近。
接下来说说我碰到什么问题。
这段代码在最开始是能够正常的跑通的,没有任何问题。可是后来我希望API能够返回识别结果对应的百科信息,于是在传给服务器的参数中加入了一个baike_num
字段,并赋值为6,如下:
let postData = qs.stringify( {
image: imgbase64,
baike_num: "6"
});
结果就是云函数报错了,错误信息说是错误出在JSON.parse
的时候。接着我把baike_num
的值改为3,结果是有些识别结果可以正常返回,有些则不行。当改为1时,就又完全没有问题了。
这个结果令我百思不得其解。于是我把接受到的数据(Buffer对象)直接转成字符串打印出来。我发现打印出来的字符串是不完整的,这也就难怪为啥JSON.parse
会报错了,因为接受到的根本就不是一个完整的JSON字符串。
再仔细看代码,我突然想到,http.request()
回调函数中的res
对象是一个HTTPIncomingMessage
对象,而这个对象是继承自Stream
的,而流对象在接收数据的时候可能会多次触发data
事件。而我的代码在第一次data
事件触发的时候就已经把数据返回,后面传输的数据自然也就没有接收到,而JSON字符串也注定不会完整。
这也解释了为啥当baike_num
为6的时候云函数会报错,而baike_num
为1的时候却不会,这是因为baike_num
为1的时候,需要返回的数据量相对较小,所以能一次传输完成。
于是我把代码改成了如下:
let bufferArr = [];
let req = https.request(options, function (res) {
res.on('data', (data) => {
bufferArr.push(data);
});
res.on('end', ()=>{
let decoder = new StringDecoder();
let finalData = Buffer.concat(bufferArr);
resolve(JSON.parse(decoder.write(finalData)));
});
res.on('error', (err)=>{
reject(err);
});
});
req.write(postData);
req.end();
我用一个数组将每次data
事件触发时接收到的数据存起来,等到了end
事件触发时再将它们拼接起来。
心想着这样应该没有什么问题了的我美滋滋的开始测试,前几次测试均完美,正当我以为已经没有问题时候,同样的错误再次出现。
当时的我已经要抓狂了,因为怎么看好像都是没有问题的啊。再次翻看打印出的bufferArr
,我惊讶地发现数组中的数据竟然出现了重复!那么拼接后的数据所生成的JSON字符串也自然是不合法的。
可是这是为什么呢?我再一次进行测试,发现数组中又多了同样的数据。这时我忽然反应过来,为什么数组里还会存在上一次识别的数据?难道是每次调用云函数的时候,云端执行的都是同一个函数实例?
于是我res
的end
事件处理函数中加了一句bufferArr.length = 0
,即把数组清空。再次测试一切正常。
后来我在微信的官方文档中看到这么一段话。
云函数应是无状态的,幂等的,即一次云函数的执行不依赖上一次云函数执行过程中在运行环境中残留的信息。
为了保证负载均衡,云函数平台会根据当前负载情况控制云函数实例的数量,并且会在一些情况下重用云函数实例,这使得连续两次云函数调用如果都由同一个云函数实例运行,那么两者会共享同一个临时磁盘空间,但因为云函数实例随时可能被销毁,并且连续的请求不一定会落在同一个实例,因此云函数不应依赖之前云函数调用中在临时磁盘空间遗留的数据。总的原则即是云函数代码应是无状态的。
那么这样就能解释为啥数组里会有上一次执行时的数据了。
总结一下,这次开发实际上我是踩了两个坑,从一个坑到了另一个坑。而这两个坑说到底也都是因为自己粗心所导致的。第一个坑没有注意到数据可能会不能一次接收完,第二个坑是没有认真阅读官方文档。大家引以为戒。