เกิดข้อผิดพลาดในการจัดการกับสตรีม node.js


164

วิธีที่ถูกต้องในการจัดการข้อผิดพลาดกับสตรีมคืออะไร ฉันรู้แล้วว่ามี 'ข้อผิดพลาด' เหตุการณ์ที่คุณสามารถฟังได้ แต่ฉันต้องการทราบรายละเอียดเพิ่มเติมเกี่ยวกับสถานการณ์ที่ซับซ้อนโดยพลการ

สำหรับผู้เริ่มต้นคุณต้องทำอย่างไรเมื่อคุณต้องการทำโซ่ท่ออย่างง่าย:

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

และคุณสร้างหนึ่งในการแปลงเหล่านั้นอย่างถูกต้องอย่างไร

คำถามที่เกี่ยวข้องเพิ่มเติม:

  • เมื่อเกิดข้อผิดพลาดจะเกิดอะไรขึ้นกับเหตุการณ์ 'สิ้นสุด' มันไม่เคยโดนไล่ออก? บางครั้งมันถูกไล่ออก? มันขึ้นอยู่กับการแปลง / กระแส? มาตรฐานที่นี่มีอะไรบ้าง
  • มีกลไกใดที่ช่วยให้เกิดข้อผิดพลาดในท่อหรือไม่
  • โดเมนแก้ปัญหานี้ได้อย่างมีประสิทธิภาพหรือไม่ ตัวอย่างจะดี
  • ข้อผิดพลาดที่เกิดจากเหตุการณ์ 'ข้อผิดพลาด' มีการติดตามสแต็กหรือไม่ บางครั้ง? ไม่เคย? มีวิธีการรับหนึ่งจากพวกเขา

1
นี่ไม่ใช่เรื่องเล็กน้อย Promiseเฟรมเวิร์กทำให้ง่ายขึ้นมาก
slezica

27
แต่น่าเสียดายที่สัญญา / ฟิวเจอร์สไม่สามารถจริงๆช่วยให้คุณมีลำธาร ...
BT

คำตอบ:


222

แปลง

สตรีมการแปลงนั้นสามารถอ่านได้และเขียนได้ดังนั้นจึงเป็นสตรีม 'กลาง' ที่ดีมาก ด้วยเหตุนี้บางครั้งพวกเขาถูกเรียกว่าthroughสตรีม พวกมันคล้ายกับดูเพล็กซ์สตรีมด้วยวิธีนี้ยกเว้นมีอินเตอร์เฟสที่ดีในการจัดการข้อมูลแทนที่จะส่งผ่านเท่านั้น วัตถุประสงค์ของการแปลงกระแสข้อมูลคือการจัดการข้อมูลตามที่ได้ถูกส่งไปยังกระแสข้อมูล คุณอาจต้องการที่จะทำการโทรแบบ async บางอย่างหรือรับบางฟิลด์ทำการแมปบางอย่าง ฯลฯ


ที่ซึ่งคุณสามารถใส่กระแสการแปลง


สำหรับวิธีการสร้างการแปลงกระแสดูที่นี่และที่นี่ สิ่งที่คุณต้องทำคือ:

  1. รวมถึงโมดูลกระแส
  2. ยกตัวอย่าง (หรือสืบทอดจาก) คลาส Transform
  3. ใช้วิธีการที่ใช้_transform(chunk, encoding, callback)

อันคือข้อมูลของคุณ objectMode = trueส่วนใหญ่เวลาที่คุณจะไม่ต้องกังวลเกี่ยวกับการเข้ารหัสถ้าคุณกำลังทำงานอยู่ใน โทรกลับถูกเรียกเมื่อคุณเสร็จสิ้นการประมวลผลอัน อันนี้จะถูกผลักไปยังกระแสต่อไป

หากคุณต้องการโมดูลช่วยที่ดีที่จะช่วยให้คุณสามารถทำผ่านกระแสได้อย่างง่ายดายจริงๆผมขอแนะนำให้through2

สำหรับการจัดการข้อผิดพลาดโปรดอ่านต่อไป

ท่อ

ในห่วงโซ่ท่อการจัดการข้อผิดพลาดย่อมไม่ใช่เรื่องเล็กน้อย ตามหัวข้อ.ท่อ () นี้ไม่ได้ถูกสร้างขึ้นเพื่อส่งต่อข้อผิดพลาด ดังนั้นสิ่งที่ชอบ ...

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

คำตอบนี้เขียนอย่างสมบูรณ์แบบ ฉันจะตรวจสอบคำแนะนำโดเมน - ดูเหมือนจะเป็นวิธีการแก้ปัญหาที่ฉันกำลังมองหา
Semicolon

12
โปรดทราบว่าคุณไม่จำเป็นต้องปิด.on('error')ตัวจัดการในฟังก์ชันที่ไม่ระบุตัวตนเช่นa.on('error', function(e){handleError(e)})สามารถเป็นได้a.on('error', handleError)
timoxley

28

หากคุณกำลังใช้โหนด> = 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นี้สำหรับการสนทนาเพิ่มเติม


1
ทำไมถึงต้องใช้finishedเมื่อpipelineมีการติดต่อกลับ
Marcos Pereira

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 และอธิษฐานว่าใครก็ตามที่เขียน libs สตรีมมิ่ง 3 แบบที่แตกต่างกันได้ดำเนินการจัดการข้อผิดพลาดอย่างถูกต้อง
Alexander Mills

4
@Alex Mills 1) ปัญหาของการจัดการ 3 เหตุการณ์คืออะไรและทำไมพวกเขาถึง "แตกต่าง" เมื่อประเภทของพวกเขาเหมือนกัน - errorหนึ่งอาจรวมอยู่ในเหตุการณ์จริงแล้วแต่ละเหตุการณ์นั้นแตกต่างกัน 2) libs สตรีมมิ่งใดที่เขียนไว้ด้านบนนอกเหนือจากฟังก์ชัน Node.js ดั้งเดิม และ 3) ทำไมมันถึงสำคัญว่าพวกเขาจัดการเหตุการณ์ภายในอย่างไรเมื่อสิ่งนี้ช่วยให้ทุกคนสามารถแนบตัวจัดการข้อผิดพลาดเพิ่มเติมที่ด้านบนของสิ่งที่มีอยู่แล้วได้หรือไม่
พฤศจิกายน

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)ดูแลเฉพาะข้อผิดพลาดของกระแส แต่ถ้าคุณใช้การแปลงกระแสที่กำหนดเอง.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

ใช้multipipeแพคเกจที่จะ Combinate ลำธารหลายสายเข้าสู่กระแสเพล็กซ์หนึ่ง และจัดการข้อผิดพลาดในที่เดียว

const pipe = require('multipipe')

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


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

1

ใช้รูปแบบ Node.js โดยสร้างกลไกการแปลงกระแสและเรียกการเรียกกลับ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

ลองจับไม่ได้จับข้อผิดพลาดที่เกิดขึ้นในกระแสเพราะพวกเขาจะถูกโยนหลังจากรหัสโทรได้ออกแล้ว คุณสามารถอ้างถึงเอกสาร:

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


ขอบคุณ แต่นี่ไม่ได้ตอบคำถามเลย
BT

การให้เอกสาร 40 หน้าแก่ฉันไม่เป็นประโยชน์ คุณคิดว่าฉันควรอ้างอิงอะไรในหน้ายักษ์ นอกจากนี้คุณได้อ่านคำถามของฉัน คำถามของฉันไม่ใช่ "ลองจับคู่กับสตรีมได้หรือไม่" ฉันทราบดีอยู่แล้วว่าการลองจับจะไม่ทำงานกับข้อผิดพลาดแบบอะซิงโครนัสเช่นจากการประมวลผลท่อส่งกระแสข้อมูล
BT
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.