Каков правильный способ обработки ошибок с потоками? Я уже знаю, что есть событие "ошибки", которое вы можете прослушать, но я хочу узнать более подробную информацию о произвольно сложных ситуациях.
Для начала, что вы делаете, когда хотите сделать простую цепочку труб:
input.pipe(transformA).pipe(transformB).pipe(transformC)...
И как вы правильно создаете одно из этих преобразований, чтобы ошибки обрабатывались правильно?
Другие связанные вопросы:
- когда происходит ошибка, что происходит с событием "end"? Это никогда не срабатывает? Иногда его увольняют? Это зависит от преобразования/потока? Каковы стандарты здесь?
- Существуют ли какие-либо механизмы для ошибок распространения через трубы?
- Домены эффективно решают эту проблему? Примеры были бы приятными.
- Ошибки, возникающие из событий "error", имеют трассировки стека? Иногда? Никогда? есть ли способ получить их от них?
Ответ 1
преобразование
Потоки преобразования являются как читаемыми, так и записываемыми, и поэтому являются действительно хорошими "средними" потоками. По этой причине их иногда называют потоками through
. В этом смысле они похожи на дуплексный поток, за исключением того, что они предоставляют хороший интерфейс для манипулирования данными, а не просто для их отправки. Целью потока преобразования является манипулирование данными при их передаче по потоку. Вы можете, например, сделать несколько асинхронных вызовов или получить пару полей, переназначить некоторые вещи и т.д.
![Where you might put a transform stream]()
Для получения информации о том, как создать поток преобразования, смотрите здесь и здесь. Все, что вам нужно сделать, это:
- включить модуль потока
- создавать (или наследовать) класс Transform
- реализовать метод
_transform
, который принимает (chunk, encoding, callback)
.
Кусок - это ваши данные. Большую часть времени вам не нужно беспокоиться о кодировании, если вы работаете в objectMode = true
. Обратный вызов вызывается, когда вы закончите обработку чанка. Затем этот фрагмент передается следующему потоку.
Если вам нужен хороший вспомогательный модуль, который позволит вам действительно легко выполнять потоковую передачу, я предлагаю –2.
Для обработки ошибок продолжайте читать.
pipe
В цепочке pipeопроводов обработка ошибок действительно нетривиальна. Согласно этот поток.pipe() не создан для пересылки ошибок. Так что-то вроде...
var a = createStream();
a.pipe(b).pipe(c).on('error', function(e){handleError(e)});
... будет прослушивать только ошибки в потоке c
. Если на a
было сгенерировано событие ошибки, оно не будет передано и, фактически, сгенерирует. Чтобы сделать это правильно:
var a = createStream();
a.on('error', function(e){handleError(e)})
.pipe(b)
.on('error', function(e){handleError(e)})
.pipe(c)
.on('error', function(e){handleError(e)});
Теперь, хотя второй способ более многословен, вы можете, по крайней мере, сохранить контекст, в котором происходят ваши ошибки. Обычно это хорошая вещь.
Одна библиотека, которую я считаю полезной, если у вас есть случай, когда вы хотите захватить только ошибки в месте назначения, и вас не очень волнует, где это произошло, - поток событий.
конец
При возникновении события ошибки конечное событие не будет запущено (явно). Передача события ошибки завершит поток.
домены
По моему опыту, домены работают очень хорошо большую часть времени. Если у вас есть необработанное событие ошибки (то есть сообщение об ошибке в потоке без прослушивателя), сервер может дать сбой. Теперь, как указано в приведенной выше статье, вы можете заключить поток в домен, который должен правильно отлавливать все ошибки.
var d = domain.create();
d.on('error', handleAllErrors);
d.run(function() {
fs.createReadStream(tarball)
.pipe(gzip.Gunzip())
.pipe(tar.Extract({ path: targetPath }))
.on('close', cb);
});
Прелесть доменов в том, что они сохранят следы стека. Хотя Event-Stream также хорошо справляется с этой задачей.
Для дальнейшего чтения ознакомьтесь с потоковым справочником. Довольно подробно, но очень полезно и дает отличные ссылки на множество полезных модулей.
Ответ 2
Домены устарели. вам они не нужны.
для этого вопроса различия между трансформируемым или записываемым не так важны.
mshell_lauren ответ велик, но в качестве альтернативы вы также можете явно прослушивать событие ошибки в каждом потоке, который, по вашему мнению, может быть ошибкой. и повторное использование функции обработчика, если вы предпочитаете.
var a = createReadableStream()
var b = anotherTypeOfStream()
var c = createWriteStream()
a.on('error', handler)
b.on('error', handler)
c.on('error', handler)
a.pipe(b).pipe(c)
function handler (err) { console.log(err) }
это предотвращает печально известное неперехваченное исключение, если один из этих потоков запускает свое событие ошибки
Ответ 3
Если вы используете узел> = v10.0.0, вы можете использовать stream.pipeline и stream.finished.
Например:
const { pipeline, finished } = require('stream');
pipeline(
input,
transformA,
transformB,
transformC,
(err) => {
if (err) {
console.error('Pipeline failed', err);
} else {
console.log('Pipeline succeeded');
}
});
finished(input, (err) => {
if (err) {
console.error('Stream failed', err);
} else {
console.log('Stream is done reading');
}
});
Смотрите этот github PR для дальнейшего обсуждения.
Ответ 4
Ошибки всей цепочки могут быть переданы в самый правый поток с помощью простой функции:
function safePipe (readable, transforms) {
while (transforms.length > 0) {
var new_readable = transforms.shift();
readable.on("error", function(e) { new_readable.emit("error", e); });
readable.pipe(new_readable);
readable = new_readable;
}
return readable;
}
который можно использовать как:
safePipe(readable, [ transform1, transform2, ... ]);
Ответ 5
.on("error", handler)
заботится только об ошибках потока, но если вы используете пользовательские потоки преобразования, .on("error", handler)
не перехватывает ошибки, возникающие внутри функции _transform
. Таким образом, можно сделать что-то вроде этого для управления потоком приложений:
this
ключевое слово в функции _transform
относится к самому Stream
, который является EventEmitter
. Таким образом, вы можете использовать try catch
как try catch
ниже, чтобы перехватить ошибки и затем передать их в пользовательские обработчики событий.
// CustomTransform.js
CustomTransformStream.prototype._transform = function (data, enc, done) {
var stream = this
try {
// Do your transform code
} catch (e) {
// Now based on the error type, with an if or switch statement
stream.emit("CTError1", e)
stream.emit("CTError2", e)
}
done()
}
// StreamImplementation.js
someReadStream
.pipe(CustomTransformStream)
.on("CTError1", function (e) { console.log(e) })
.on("CTError2", function (e) { /*Lets do something else*/ })
.pipe(someWriteStream)
Таким образом, вы можете хранить свою логику и обработчики ошибок отдельно. Кроме того, вы можете обрабатывать только некоторые ошибки и игнорировать другие.
ОБНОВИТЬ
Альтернатива: наблюдаемый RXJS
Ответ 6
Используйте шаблон Node.js, создав механику потока Transform и вызывая ее обратный вызов done
с аргументом для распространения ошибки:
var transformStream1 = new stream.Transform(/*{objectMode: true}*/);
transformStream1.prototype._transform = function (chunk, encoding, done) {
//var stream = this;
try {
// Do your transform code
/* ... */
} catch (error) {
// nodejs style for propagating an error
return done(error);
}
// Here, everything went well
done();
}
// Let use the transform stream, assuming `someReadStream`
// and `someWriteStream` have been defined before
someReadStream
.pipe(transformStream1)
.on('error', function (error) {
console.error('Error in transformStream1:');
console.error(error);
process.exit(-1);
})
.pipe(someWriteStream)
.on('close', function () {
console.log('OK.');
process.exit();
})
.on('error', function (error) {
console.error(error);
process.exit(-1);
});
Ответ 7
Try catch не будет регистрировать ошибки, которые произошли в потоке, потому что они генерируются после того, как вызывающий код уже завершился. Вы можете обратиться к документации:
https://nodejs.org/dist/latest-v10.x/docs/api/errors.html