Xử lý lỗi với luồng node.js


164

Cách chính xác để xử lý lỗi với luồng? Tôi đã biết có một sự kiện "lỗi" mà bạn có thể nghe, nhưng tôi muốn biết thêm một số chi tiết về các tình huống phức tạp tùy ý.

Để bắt đầu, bạn sẽ làm gì khi bạn muốn làm một chuỗi ống đơn giản:

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

Và làm thế nào để bạn tạo đúng một trong những biến đổi đó để các lỗi được xử lý chính xác?

Các câu hỏi liên quan khác:

  • khi xảy ra lỗi, điều gì xảy ra với sự kiện 'kết thúc'? Nó không bao giờ bị sa thải? Có đôi khi nó bị sa thải? Nó phụ thuộc vào biến đổi / luồng? Các tiêu chuẩn ở đây là gì?
  • Có bất kỳ cơ chế để đẩy lỗi thông qua các đường ống?
  • làm miền giải quyết vấn đề này một cách hiệu quả? Ví dụ sẽ tốt đẹp.
  • các lỗi phát sinh từ các sự kiện 'lỗi' có dấu vết ngăn xếp không? Đôi khi? Không bao giờ? Có cách nào để có được một từ họ?

1
Đây không phải là chuyện nhỏ. Promisecác khung làm cho nó đơn giản hơn rất nhiều
slezica

27
Thật không may, những lời hứa / tương lai không thể thực sự giúp bạn với các luồng ...
BT

Câu trả lời:


222

biến đổi

Các luồng chuyển đổi đều có thể đọc và ghi được, và do đó là các luồng 'trung bình' thực sự tốt. Vì lý do này, đôi khi chúng được gọi là throughluồng. Chúng tương tự như một luồng song công theo cách này, ngoại trừ chúng cung cấp một giao diện đẹp để thao tác dữ liệu thay vì chỉ gửi nó qua. Mục đích của luồng biến đổi là thao tác dữ liệu khi nó được dẫn qua luồng. Ví dụ, bạn có thể muốn thực hiện một số cuộc gọi không đồng bộ hoặc lấy một vài trường, sắp xếp lại một số thứ, v.v.


Nơi bạn có thể đặt một luồng biến đổi


Để biết cách tạo một luồng biến đổi, hãy xem tại đâyđây . Tât cả nhưng điêu bạn phải lam la :

  1. bao gồm các mô-đun dòng
  2. khởi tạo (hoặc kế thừa từ) lớp Transform
  3. thực hiện một _transformphương pháp trong đó có một (chunk, encoding, callback).

Chunk là dữ liệu của bạn. Hầu hết thời gian bạn sẽ không cần phải lo lắng về mã hóa nếu bạn đang làm việc trongobjectMode = true . Cuộc gọi lại được gọi khi bạn xử lý xong đoạn. Đoạn này sau đó được đẩy sang luồng tiếp theo.

Nếu bạn muốn một mô-đun trình trợ giúp đẹp cho phép bạn thực hiện thông qua luồng thực sự dễ dàng, tôi đề nghị thông qua2 .

Để xử lý lỗi, tiếp tục đọc.

ống

Trong một chuỗi ống, xử lý lỗi thực sự là không tầm thường. Theo chủ đề này .pipe () không được xây dựng để chuyển tiếp lỗi. Vì vậy, một cái gì đó như ...

var a = createStream();
a.pipe(b).pipe(c).on('error', function(e){handleError(e)});

... sẽ chỉ lắng nghe lỗi trên luồng c. Nếu một sự kiện lỗi được phát ra a, điều đó sẽ không được truyền lại và trên thực tế, sẽ xảy ra. Để làm điều này một cách chính xác:

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)});

Bây giờ, mặc dù cách thứ hai dài dòng hơn, ít nhất bạn có thể giữ bối cảnh nơi xảy ra lỗi của mình. Đây thường là một điều tốt.

Một thư viện tôi thấy hữu ích mặc dù nếu bạn gặp trường hợp bạn chỉ muốn ghi lại các lỗi ở đích và bạn không quan tâm lắm đến việc nó xảy ra ở đâu là luồng sự kiện .

kết thúc

Khi một sự kiện lỗi được kích hoạt, sự kiện kết thúc sẽ không được kích hoạt (một cách rõ ràng). Việc phát ra một sự kiện lỗi sẽ kết thúc luồng.

tên miền

Theo kinh nghiệm của tôi, các miền hoạt động thực sự tốt hầu hết thời gian. Nếu bạn có một sự kiện lỗi chưa được xử lý (nghĩa là phát ra lỗi trên luồng mà không có người nghe), máy chủ có thể gặp sự cố. Bây giờ, như bài viết trên chỉ ra, bạn có thể gói luồng trong một miền cần nắm bắt đúng tất cả các lỗi.

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);
 });

Vẻ đẹp của tên miền là chúng sẽ lưu giữ các dấu vết ngăn xếp. Mặc dù dòng sự kiện cũng làm tốt công việc này.

Để đọc thêm, hãy xem cuốn sổ tay stream . Khá sâu sắc, nhưng siêu hữu ích và cung cấp một số liên kết tuyệt vời đến nhiều mô-đun hữu ích.


Đó là thông tin thực sự tuyệt vời, cảm ơn! Bạn có thể thêm một chút về lý do tại sao bạn muốn tạo một luồng biến đổi và tại sao nó liên quan đến câu hỏi của tôi không?
BT

Chắc chắn - mặc dù tôi đã tìm ra nó liên quan từ khi bạn hỏi về nó; )
mshell_lauren

1
Đăng bài này bằng isaccs trên Google Groups- nodejs: Groups.google.com/d/msg/nodejs/lJYT9hZxFu0/L59CFbqWGyYJ (không phải grokbase)
jpillora

Câu trả lời này được viết hoàn hảo. Tôi sẽ điều tra đề xuất tên miền - nó dường như là loại giải pháp tôi đang tìm kiếm.
Dấu chấm phẩy

12
Lưu ý rằng bạn không cần phải bọc .on('error')trình xử lý trong một hàm ẩn danh, tức là a.on('error', function(e){handleError(e)})chỉ có thể làa.on('error', handleError)
timoxley

28

Nếu bạn đang sử dụng nút> = v10.0.0, bạn có thể sử dụng stream.pipelinestream.finished .

Ví dụ:

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');
  }
});

Xem PR github này để thảo luận thêm.


1
Tại sao bạn sẽ sử dụng finishedmặc dù, khi pipelineđã có một cuộc gọi lại?
Marcos Pereira

4
Bạn có thể muốn xử lý các lỗi khác nhau giữa các đường ống và các luồng riêng lẻ.
shusson

25

tên miền bị phản đối. bạn không cần chúng

đối với câu hỏi này, sự khác biệt giữa biến đổi hoặc ghi được không quá quan trọng.

Câu trả lời của mshell_lauren là tuyệt vời, nhưng như một cách thay thế, bạn cũng có thể lắng nghe rõ ràng sự kiện lỗi trên mỗi luồng mà bạn nghĩ có thể có lỗi. và tái sử dụng chức năng xử lý nếu bạn thích.

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) }

làm như vậy để ngăn chặn ngoại lệ không bị khét tiếng nên một trong những luồng đó phát sinh sự kiện lỗi của nó


3
lol vui vẻ xử lý 3 sự kiện lỗi khác nhau và cầu nguyện rằng bất cứ ai đã viết 3 lib phát trực tuyến khác nhau đều thực hiện xử lý lỗi một cách chính xác
Alexander Mills

4
@Alex Mills 1) Vấn đề xử lý 3 sự kiện là gì và tại sao chúng lại "khác nhau", khi loại của chúng giống nhau - error, người ta cũng có thể giải quyết với thực tế là mỗi sự kiện là khác nhau; 2) libs phát trực tuyến nào được viết ở trên, ngoài chức năng Node.js gốc? và 3) tại sao việc họ xử lý các sự kiện trong nội bộ lại quan trọng như thế nào, khi điều này rõ ràng cho phép bất cứ ai gắn các trình xử lý lỗi bổ sung lên trên bất cứ thứ gì đã có sẵn?
Amn

10

Lỗi từ toàn bộ chuỗi có thể được truyền đến luồng ngoài cùng bên phải bằng một hàm đơn giản:

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;
}

có thể được sử dụng như:

safePipe(readable, [ transform1, transform2, ... ]);

5

.on("error", handler)chỉ quan tâm đến các lỗi Luồng nhưng nếu bạn đang sử dụng các luồng Chuyển đổi tùy chỉnh, .on("error", handler)đừng bắt lỗi xảy ra bên trong _transformchức năng. Vì vậy, người ta có thể làm một cái gì đó như thế này để kiểm soát luồng ứng dụng: -

thistừ khóa trong _transformhàm đề cập đến Streamchính nó, đó là một EventEmitter. Vì vậy, bạn có thể sử dụng try catchnhư dưới đây để bắt lỗi và sau đó chuyển chúng cho các trình xử lý sự kiện tùy chỉnh.

// 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)

Bằng cách này, bạn có thể tách biệt các trình xử lý logic và lỗi của mình. Ngoài ra, bạn có thể chọn chỉ xử lý một số lỗi và bỏ qua các lỗi khác.

CẬP NHẬT
thay thế: RXJS có thể quan sát


4

Sử dụng multipipe gói để combinate nhiều luồng lại thành một dòng song. Và xử lý lỗi ở một nơi.

const pipe = require('multipipe')

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


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

1

Sử dụng mẫu Node.js bằng cách tạo cơ chế chuyển đổi luồng và gọi lại cuộc gọi của nó donebằng một đối số để truyền lỗi:

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);
   });

Hmm, vì vậy bạn đang nói nếu tất cả các bộ xử lý luồng được xây dựng như thế này, lỗi sẽ lan truyền?
BT

-2

Hãy thử bắt sẽ không nắm bắt được các lỗi xảy ra trong luồng vì chúng bị ném sau khi mã cuộc gọi đã thoát. bạn có thể tham khảo tài liệu:

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


Cảm ơn, nhưng điều này không trả lời câu hỏi nào cả.
BT

Cung cấp cho tôi một tài liệu 40 trang là không hữu ích. Bạn nghĩ tôi nên đề cập đến điều gì trong trang khổng lồ đó? Ngoài ra, bạn đã đọc câu hỏi của tôi? Câu hỏi của tôi không phải là "có thử bắt công việc với các luồng không?" Tôi đã nhận thức rõ rằng việc thử bắt sẽ không hoạt động với các lỗi không đồng bộ, ví dụ: các lỗi từ các đường ống xử lý luồng.
BT
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.