Поддерживает ли библиотека Node.js "request" поток асинхронных итераций?
Я немного новичок в библиотеках 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
переменная getChunks()
не асинхронно-итеративный.
TypeError: поток не асинхронный итеративный
Это удивительно, так как я думал, что все читаемые потоки, как правило, асинхронно-итерируемы, и что библиотека запросов возвращает поток, когда обратный вызов не предоставляется. Когда я заменяю request.get(...)
с fs.createReadStream(...)
в какой-то локальный файл все работает как положено.
Возможно, request
Библиотека не поддерживает это. Если так, что мне нужно сделать, чтобы обрабатывать потоки ответов HTTP с помощью асинхронной итерации?
Использование Node.js 11.13 и request
2.88.0.
3 ответа
Я сделал еще несколько экспериментов с request
а также request-promise-native
библиотеки и не думаю, что это возможно при текущей реализации. Результирующий поток вообще не выглядит асинхронно-итерируемым. Кроме того, правильная реализация должна await
для возврата ответа до обработки потока (как предложено в ответе @JBone). Но если вы позвоните 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);
}
}
Простой ответ: нет, это не так. Возможно, вы захотите использовать обертку на основе обещаний вокруг request
например запрос-обещание, которое затем также работает с async
/await
,
Детали: обратите внимание, что request
было объявлено устаревшим его создателем и, следовательно, будет прекращено. Это означает, что рано или поздно вам, скорее всего, придется переключиться на другое решение, такое как axios, superagent или игла, чтобы назвать несколько.
Конечно, вы должны оценить эти модули и выяснить, что лучше всего соответствует вашим потребностям, но моя личная рекомендация будет начинать с axios
, поскольку у меня был очень хороший опыт в прошлом, однако, YMMV.
Похоже, вам придется использовать другие альтернативы, как они упоминали в 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();
}
});
}
Параметр потока axios не работал у меня, используя образец кода в приведенном выше ответе на axios 0.19.0. Может быть проблема между креслом и клавиатурой, но в любом случае... вот альтернативный подход с использованиемrequest
.
Я закончил тем, что адаптировал потоковую передачу запросов к асинхронному генератору (конечно, с буфером). Это позволяет использовать интерфейс "потокового" типа, в котором чтение и запись данных могут чередоваться... это не гарантирует низкое потребление памяти. запрашивать каналы ("подталкивать") к нашему Writable так быстро, как только возможно, и у нас нет возможности приостановить это или превратить его в интерфейс типа "pull" (насколько я знаю). Итак, если мы читаем данные из буфера медленнее, чем данные записываются: буфер станет очень большим, а использование памяти будет высоким.
Поэтому, если критически важно снизить использование памяти и вы анализируете большие файлы из источников http... тогда, вероятно, выполняете некоторый мониторинг / отчет о размере буфера во время "потоковой передачи", чтобы увидеть, является ли ваш код потребления быстрее или медленнее, чем поток чтобы вы знали, станет ли буфер огромным или останется маленьким. Конечно, если вы тестируете очень медленный http-сервер... тогда все ставки отключены.
Это можно было бы решить, установив фиксированный размер буфера и сделав _write
block, пока не произойдет еще какое-то чтение (освобождение места в буфере)... т.е. запрос должен ждать, чтобы записать больше данных в канал. Однако запрос может буферизоваться внутри... так что это не поможет с потреблением памяти, если данные все равно накапливаются на конце запроса. Придется проверить.
Образец кода:
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(...)
)