我对Node.js库有些陌生,我正在尝试弄清楚如何在HTTP响应流上使用异步迭代。我的总体目标是读取大量的响应流,并在块到达时对其进行处理,当前是通过生成器函数进行的。我无法将整个响应存储在内存中进行处理。
我正在使用request
库来执行HTTP请求,如下所示。
const request = require("request");
// contrived chunk-by-chunk stream processing
async function* getChunks(stream) {
for await (const chunk of stream) {
yield chunk[0];
}
}
async function doWork() {
var response = request.get("https://pastebin.com/raw/x4Nn0Tby");
for await (c of getChunks(response)) {
console.log(c);
}
}
运行doWork()
时,出现错误,指出stream
的{{1}}变量不是异步可迭代的。
TypeError:流不可异步迭代
这是令人惊讶的,因为我认为所有可读流通常都是异步可迭代的,并且当没有提供回调时,请求库将返回流。当我将getChunks()
替换为request.get(...)
到某个本地文件时,所有文件都按预期工作。
也许fs.createReadStream(...)
库不支持此功能。如果是这样,我该怎么办才能通过async-iteration处理HTTP响应流?
使用Node.js 11.13和request
2.88.0。
答案 0 :(得分:1)
我对request
和request-promise-native
库进行了更多实验,并且认为在当前实现中这是不可能的。结果流似乎根本不是异步可迭代的。此外,在处理流之前,需要await
的适当实现才能使响应返回(如@JBone's answer所建议)。但是,如果调用await request.get(...)
,则会检索到响应的全部内容,这对于大型响应是不希望的。
const r = require("request");
const rpn = require("request-promise-native");
// contrived chunk-by-chunk stream processing
async function* getChunks(stream) {
for await (const chunk of stream) {
yield chunk[0];
}
}
async function doWork() {
const url = "https://pastebin.com/raw/x4Nn0Tby";
const response = r.get(url); // returns a non-async-iterable object.
const response2 = await rp.get(url); // returns the contents of url
for await (c of getChunks(response)) { // yields response not async-iterable error.
console.log(c);
}
}
我对此问题的解决方案是将request
和request-promise-native
的用法替换为axios
库。这些库在功能上相似,但是axios
允许您指定请求应解析为流;如预期的那样,该流是异步可迭代的。
const axios = require("axios");
async function doWork() {
var response = await axios.request({
method: "GET",
url: "https://pastebin.com/raw/x4Nn0Tby",
responseType: "stream",
});
for await (c of getChunks(response)) { // async-iteration over response works as expected.
console.log(c);
}
}
答案 1 :(得分:1)
简单的回答:不,不是。您可能想在request
周围使用基于承诺的包装器,例如request-promise,然后再与async
/ await
一起使用。
详细信息:请注意,request
已经是deprecated by its creator,因此将不再使用。这意味着,迟早,您很可能需要切换到其他解决方案,例如axios,superagent或needle,等等。
当然,您需要评估这些模块并确定最适合您的需求,但是我个人的建议是从axios
开始,因为我过去对此有很好的经验。 ,YMMV。
答案 2 :(得分:0)
似乎您将不得不使用其他替代方法,就像在request
模块文档中提到的那样,您可以在此处找到
https://www.npmjs.com/package/request
request supports both streaming and callback interfaces natively. If you'd like
request to return a Promise instead, you can use an alternative interface wrapper for
request. These wrappers can be useful if you prefer to work with Promises, or if
you'd like to use async/await in ES2017.
Several alternative interfaces are provided by the request team, including:
request-promise (uses Bluebird Promises)
request-promise-native (uses native Promises)
request-promise-any (uses any-promise Promises)`
我对以下问题的回答:
我认为您可以创建async await
个自定义方法来实现它。
async function doMyWork() {
try {
const response = await myOwnRequest(url);
} catch (e) {
console.log ('the error', e);
}
}
function myOwnRequest(url) {
return new Promise(function (resolve, reject) {
const resp = request.get(url);
if(resp) {
resolve();
} else {
reject();
}
});
}
答案 3 :(得分:0)
使用上面关于axios 0.19.0的答案中的示例代码,axios的流选项对我不起作用。椅子和键盘之间可能存在问题,但是无论如何...这是使用request
的替代方法。
我最终将请求流调整为异步生成器(当然之间有一个缓冲区)。 这允许使用“流”类型的接口,在该接口中可以对数据的读写进行交错处理……这不能保证低内存消耗。尽可能快地请求管道(“推送”)到我们的Writable,并且没有办法让我们暂停它或将其翻转为“拉”类型的接口(据我所知)。因此,如果我们从缓冲区中读取数据的速度比写入数据的速度慢:缓冲区将变得非常大,内存使用率将会很高。
因此,降低内存使用量至关重要,并且您从http来源解析大文件...然后可能在“流式传输”时对缓冲区大小进行一些监视/报告,以查看消耗代码的速度是较快还是较慢而不是流,所以您知道缓冲区会变大还是变小。当然,如果您使用非常慢的http服务器进行测试...那么所有选择都将关闭。
可以通过设置固定的缓冲区大小并阻塞_write
直到发生更多读取(在缓冲区中腾出空间)来解决此问题,即请求必须等待将更多数据写入管道。但是,请求可能会在内部进行缓冲...因此,无论如何,如果数据堆积在请求的末端,这将无助于内存消耗。将不得不检查。
示例代码:
const request = require('request'),
Writable = require('stream').Writable,
EventEmitter = require('events');
module.exports = function (url, MAX_BYTES=1024) {
var response = new ResponseBuffer(MAX_BYTES);
request
.get(url)
.on('error', function(err) { throw err; })
.pipe(response)
.on('error', function(err) { throw err; });
return response.reader();
};
class ResponseBuffer extends Writable {
constructor (MAX_BYTES=1024) {
super();
this.buffer = '';
this.open = true;
this.done = null; // callback to call when done reading.
this.MAX_BYTES = MAX_BYTES;
this.events = new EventEmitter();
}
_write(chunk, enc, next) {
this.buffer += chunk;
this.events.emit('data');
next();
}
_final(done) {
this.open = false; // signal to reader to return after buffer empty.
return done();
}
async * reader () {
while (true) {
if (this.buffer.length == 0) {
// buffer empty and Writable !open. return.
if (!this.open) { return; }
else { // buffer empty. wait for data.
await new Promise(resolve => this.events.once('data', resolve));
}
}
let read_bytes = this.buffer.length < this.MAX_BYTES ? this.buffer.length : this.MAX_BYTES;
yield this.buffer.slice(0, read_bytes);
this.buffer = this.buffer.slice(read_bytes);
}
}
}
然后像这样使用它:
const httpModule = require('./path/to/above/module');
var httpGen = httpModule('https://www.google.com'),
chunk;
for await (chunk of httpGen) {
// do something with chunk.
}
另一种方法(如果您特别关注内存使用情况)是仅下载到磁盘(流式传输到文件编写器),然后从磁盘上增量读取(您可以使fs.createReadStream(...)
异步访问)