Помилка роботи з потоками node.js


164

Який правильний спосіб обробляти помилки потоками? Я вже знаю, що є подія "помилки", яку ви можете слухати, але я хочу знати більше деталей про довільно складні ситуації.

Для початку, що робити, коли ви хочете зробити простий ланцюг труб:

input.pipe(transformA).pipe(transformB).pipe(transformC)...

І як правильно створити одне з цих перетворень, щоб помилки оброблялися правильно?

Більше пов'язаних питань:

  • коли трапляється помилка, що відбувається з подією "кінець"? Він ніколи не звільняється? Чи іноді його звільняють? Це залежить від перетворення / потоку? Які тут стандарти?
  • чи існують механізми пропонування помилок через труби?
  • чи домени ефективно вирішують цю проблему? Приклади були б непогані.
  • чи мають помилки, що виходять із подій "помилки", стеки стека? Іноді? Ніколи? чи є спосіб їх отримати?

1
Це не банально. Promiseрамки роблять це набагато простіше
slezica

27
На жаль, обіцянки / ф'ючерси насправді не можуть допомогти вам із потоками ...
BT

Відповіді:


222

перетворення

Трансформаційні потоки є і читабельними, і записаними, і, таким чином, справді є хорошими середніми потоками. З цієї причини їх іноді називають throughпотоками. Вони подібні до дуплексного потоку таким чином, за винятком того, що вони надають приємний інтерфейс для маніпулювання даними, а не просто їх надсилання. Мета потоку трансформації - маніпулювати даними під час проходження через потік. Ви можете, наприклад, зробити кілька викликів асинхронізації або отримати пару полів, перезаписати деякі речі тощо.


Там, де ви можете поставити потік трансформації


Про те, як створити потік перетворень, дивіться тут і тут . Все, що вам потрібно зробити, це:

  1. включити модуль потоку
  2. інстанціювати (або успадкувати від) клас Transform
  3. реалізувати _transformметод, який приймає a (chunk, encoding, callback).

Частка - це ваші дані. Більшість часу вам не потрібно буде турбуватися про кодування, якщо ви працюєте objectMode = true. Зворотний виклик викликається, коли ви закінчите обробку шматка. Потім цей шматок переміщується на наступний потік.

Якщо ви хочете приємний модуль помічника, який дозволить вам пройти через потік дуже легко, я пропоную через2 .

Для вирішення помилок продовжуйте читати.

труба

У ланцюзі трубопроводів помилки обробки справді нетривіальні. Відповідно до цього потоку .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);
 });

Краса доменів полягає в тому, що вони збережуть сліди стека. Хоча подія-потік добре справляється і з цим.

Для подальшого читання перегляньте посібник із потоку . Досить глибоко, але дуже корисно та дає чудові посилання на безліч корисних модулів.


Це дійсно чудова інформація, дякую! Чи можете ви додати трохи про те, чому ви хочете створити потік перетворень і чому це стосується мого питання?
BT

Звичайно - хоч я вважав, що це пов’язано з тих пір, як ви про це запитали; )
mshell_lauren

1
Публікуйте про це isaccs в Google Groups- nodejs: groups.google.com/d/msg/nodejs/lJYT9hZxFu0/L59CFbqWGyYJ (не grokbase)
jpillora

Ця відповідь написана ідеально. Я збираюся дослідити пропозицію щодо домену - це, здається, саме таке рішення, яке я шукав.
напівколонка

12
Зауважте, що вам не потрібно .on('error')a.on('error', function(e){handleError(e)})a.on('error', handleError)
вкручувати

28

Якщо ви використовуєте node> = 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 для більшого обговорення.


1
Навіщо використовуватись, finishedхоча pipelineвже має зворотний дзвінок?
Маркос Перейра

4
Можливо, ви хочете по-різному обробляти помилки між трубопроводом та окремими потоками.
shusson

25

домени застарілі. вони вам не потрібні.

для цього питання розмежування між трансформаційними або записаними не такі важливі.

Відповідь 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
хай весело впорається з 3 різними подіями помилок і моліться, щоб той, хто написав 3 різні потокові лінзи, правильно здійснив обробку помилок
Alexander Mills

4
@ Алекс Міллз 1) У чому полягає проблема в обробці трьох подій, і чому вони "різні", коли тип їх однаковий - errorможна також вирішити той факт, що кожна подія відрізняється; 2) які потокові вкладки написані вище, крім власних функцій Node.js? і 3) чому важливо, як вони обробляють події всередині країни, коли це очевидно дозволяє кожному приєднувати додаткові обробники помилок поверх того, що вже є?
amn

10

Помилки з цілого ланцюга можна поширити в крайній правий потік за допомогою простої функції:

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)піклується лише про помилки потоку, але якщо ви використовуєте власні потоки Transform, .on("error", handler)не вловлюйте помилки, що відбуваються всередині _transformфункції. Тож можна зробити щось подібне для контролю потоку додатків: -

thisключове слово у _transformфункції позначає Streamсебе, що є EventEmitter. Таким чином, ви можете використовувати, 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 Спостерігається


4

Використовуйте мультипійний пакет для об'єднання декількох потоків в один дуплексний потік. І обробляти помилки в одному місці.

const pipe = require('multipipe')

// pipe streams
const stream = pipe(streamA, streamB, streamC) 


// centralized error handling
stream.on('error', fn)

1

Використовуйте шаблон 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's 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);
   });

Хм, так що ви кажете, якби всі потокові процесори були побудовані так, помилки поширюватимуться?
BT

-2

Спробуйте catch не буде фіксувати помилки, що трапилися в потоці, оскільки вони викидаються після того, як код виклику вже вийшов. Ви можете посилатися на документацію:

https://nodejs.org/dist/latest-v10.x/docs/api/errors.html


Дякую, але це зовсім не відповідає на питання.
BT

Дати мені документ на 40 сторінок не корисно. Як ви думаєте, про що я повинен звернутися в цій гігантській сторінці? Також ви читали моє запитання? Моє запитання не в тому, "чи намагається зловити роботу потоками?" Я вже добре усвідомлюю, що спроба улову не працюватиме з асинхронними помилками, наприклад, з трубопроводів потокової обробки.
BT
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.