项目中有一个下载图片的需求,现在在编写下载代码的过程中遇到了一些问题。
我的需求是进行批量下载,如果下载失败需要进行记录,然后用本地的404图片替代。 下载完的图片需要马上放入docx文件中,所以需要对下载的过程进行整体阻塞。
一开始我是使用最简单的方法进行下载
function download(src, dest) {
return new Promise((res,rej) => {
const pipe = request(src).pipe(fs.createWriteStream(dest))
pipe.on('close',() => res());
pipe.on('error',(err) => rej(err));
});
}
后面我发现, 如果目标是404的话,他会将404的信息写入到图片中,而不会有报错信息。 我的代码变成 request.get({url}, (err,res) => { if(err) return rej(err); if(res.statusCode === 404) return rej(‘404’); fs.writeFile(dest, responseBody, ‘binary’, err => { if(err) return rej(err); return res(1); }); }); 但组长说得使用流的方式写, 不然占用太多内存。 我又使用了wget,但是写完了之后发现偶尔会有一两张下载不完全,导致后面代码获取的时候出错。 查看了一下wget的代码,
var downloader = new EventEmitter(),
req = request(options, function(res) {
var fileSize, writeStream, downloadedSize;
if (res.statusCode === 200) {
downloadedSize = 0;
fileSize = res.headers['content-length'];
writeStream = fs.createWriteStream(output, {
flags: 'a',
encoding: 'binary'
});
res.on('error', function(err) {
writeStream.end();
downloader.emit('error', err);
});
res.on('data', function(chunk) {
downloadedSize += chunk.length;
downloader.emit('progress', downloadedSize/fileSize);
writeStream.write(chunk);
});
res.on('end', function() {
writeStream.end();
downloader.emit('end', output);
});
} else {
downloader.emit('error', 'Server respond ' + res.statusCode);
}
});
我的代码里是用 downloader.on(‘end’,() => res());来判断是否成功, 但是在res.on(‘end’)的方法里面,writeStream.end();结束写入了,但好像还没有彻底完成? 所以我决定这样写
downloader.on('end',async () =>{
await sleep(500);
return res();
});
发现问题解决, 但是组长说不能这么写。
所以我又开始考虑superagent 我尝试是这么写
request.get(src)
.ok(res => res.statusCode === 200)
.then(res => {
const pipe = res.pipe(writeStream);
pipe.on....
})
但是这里报错,显示 end() has already been called, so its too late to starting piping
那么现在问题来了, 怎样才能在判断了statusCode 的情况下使用流下载图片啊
request.get(src)
.on('response', res => {
if (res.statusCode === 200) {
res.pipe(writeStream);
}
});
@GaleLQ 谢谢你的回答! 我用superagent和request都试了一下 发现request可以,superagent不行,不知道这是为什么呢,明明两个都有on(‘response’,…)方法
@GaleLQ superagent 的压根没有触发 response事件
@974806047 看这里
重写了pipe等于发送请求(getFrame)加pipe行为,对你来说不可见,不改源码比较难弄,还是用别的吧
function download ( args ) {
//some code ...
return new Promise((resolve, reject) => {
try {
let rs = request({
url,
}).on('error', function (err) {
fs.appendFileSync('./failLog.log', url + '\n');
fs.writeFileSync(filePath, no_png);
reject(err);
});
rs.pipe(fs.createWriteStream(filePath));
rs.on('end', () => {
COUNT++;
resolve('Success: ' + COUNT);
})
} catch (error) {
console.log(error);
reject(error);
}
})
}
这个样子啦
@LeavesSky nice!我现在做的跟你差不多了,就是没有错误的时候fs.writeFileSync(filePath, no_png); 这一步,我都是错误了之后返回给上层,再统一替换,就不用在下载的过程中一直复制了。
@974806047 在外部有个流式任务队列, 请求频率的比较凶, 所以放在里面帮我拖慢点速度(这理由好像有点牵强 !_!
)
悄悄告诉你, 我还开了一个进程递归监测failLog.log
确保fs.writeFileSync(filePath, no_png);