NodeJS 說說「重寫」 自定義stream 的實現

概述

常見的自定義流有四種,Readable(可讀流)、Writable(可寫流)、Duplex(雙工流)和 Transform(轉換流),常見的自定義流應用有 HTTP 請求、響應, crypto 加密,進程 stdin 通信等等。

stream 模塊介紹

在 NodeJS 中要想實現自定義流,需要依賴模塊 stream ,直接引入,不需下載,所有種類的流都是繼承這個模塊內部提供的對應不同種類的類來實現的。

實現一個自定義可讀流 Readable

1、創建自定義可讀流的類 MyRead

實現自定義可讀流需創建一個類為 MyRead ,並繼承 stream 中的 Readable 類,重寫 _read方法,這是所有自定義流的固定套路。

創建自定義可讀流

const { Readable } = require("stream");
// 創建自定義可讀流的類
class MyRead extends Readable {
constructor() {
super();
this.index = 0;
}
// 重寫自定義的可讀流的 _read 方法
_read() {

this.index++;
this.push(this.index + "");
if (this.index === 3) {
this.push(null);
}
}
}複製代碼

我們自己寫的 _read 方法會先查找並執行,在讀取時使用 push 方法將數據讀取出來,直到 push 的值為 null 才會停止,否則會認為沒有讀取完成,會繼續調用 _read 。

2、驗證自定義可讀流

驗證自定義可讀流

let myRead = new MyRead();
myRead.on("data", data => {
console.log(data);
});
myRead.on("end", function() {
console.log("讀取完成");
});
//
//
//
// 讀取完成複製代碼

實現一個自定義可寫流 Writable

1、創建自定義可寫流的類 MyWrite

創建一個類名為 MyWrite ,並繼承 stream 中的 Writable 類,重寫 _write 方法。

創建自定義可寫流

const { Writable } = require("stream");
// 創建自定義可寫流的類
class MyWrite extends Writable {
// 重寫自定義的可寫流的 _write 方法
_write(chunk, encoding, callback)) {
callback(); // 將緩存區寫入文件
}
}複製代碼

寫入內容時默認第一次寫入直接寫入文件,後面的寫入都寫入緩存區,如果不調用 callback只能默認第一次寫入文件,調用 callback 會將緩存區清空並寫入文件。

2、驗證自定義可寫流

驗證自定義可寫流

let myWrite = new MyWrite();
myWrite.write("hello", "utf8", () => {
console.log("hello ok");
});
myWrite.write("world", "utf8", () => {
console.log("world ok");
});
// hello ok
// world ok複製代碼

實現一個自定義雙工流 Duplex

1、創建自定義可雙工流的類 MyDuplex

雙工流的可以理解為即可讀又可寫的流,創建一個類名為 MyDuplex ,並繼承 stream 中的 Duplex 類,由於雙工流即可讀又可寫,需重寫 _read 和 _write 方法。

創建自定雙工流

const { Duplex } = require("stream");
// 創建自定義雙工流的類
class MyDuplex extends Duplex {
// 重寫自定義的雙工流的 _read 方法
_read() {
this.push("123");
this.push(null);
}
// 重寫自定義的雙工流的 _write 方法
_write(chunk, encoding, callback)) {
callback();
}
}複製代碼

雙工流分別具備 Readable 和 Writable 的功能,但是讀和寫互不影響,互不關聯。

2、驗證自定義雙工流

驗證自定義雙工流

let myDuplex = new MyDuplex();
myDuplex.on("readable", () => {
console.log(myDuplex.read(1), "----");
});
setTimeout(() => {
myDuplex.on("data", data => {
console.log(data, "xxxx");
});
}, 3000);
// ----
// xxxx
// ----
// xxxx複製代碼

如果 readable 和 data 兩種讀取方式都使用默認先通過 data 事件讀取,所以一般只選擇一個,不要同時使用,可讀流的特點是讀取數據被消耗掉後就丟失了(緩存區被清空),如果非要兩個都用可以加一個定時器(絕對不要這樣寫)。

實現一個自定義轉化流 Transform

1、創建自定義可轉化流的類 MyTransform

轉化流的意思是即可以當作可讀流,又可以當作可寫流,創建一個類名為 MyTransform ,並繼承 stream 中的 Transform 類,重寫 _transform 方法,該方法的參數和 _write 相同。

創建自定義轉化流

const { Transform } = require('stream');
// 創建自定義轉化流的類
class MyTransform extends Transform {
// 重寫自定義的轉化流的 _transform 方法
_transform(chunk, encoding, callback)) {
console.log(chunck.toString.toUpperCase());
callback();
this.push('123');
}
}複製代碼

在自定義轉化流的 _transform 方法中,讀取數據的 push 方法和 寫入數據的 callback 都可以使用。

由此可以看出, Transform 類型可以將可讀流轉化為可寫流,也可以將可寫流轉化成可讀流,他的主要目的不是像其他類型的流一樣負責數據的讀寫,而是既作為可讀流又作為可寫流,實現流的轉化,即實現對數據的特殊處理,如 zib 模塊實現的壓縮流, cropo 模塊實現的加密流,本質都是轉化流,將轉化流作為可寫流,將存儲文件內容的可寫流通過 pipe 方法寫入轉化流,再將轉化流作為可讀流通過 pipe 方法將處理後的數據響應給瀏覽器。

2、驗證自定義轉化流

驗證自定義轉化流

let myTransForm = new MyTransform();
// 使用標準輸入
process.stdin.pipe(myTransForm).pipe(process.stdin);複製代碼

打開命令行窗口執行 node demo.js ,然後輸入 abc ,會在命令窗口輸出 ABC 和 123 ,其實轉換流先作為一個可寫流被寫入到標準輸入中,而此時 stdin 的作用是讀流,即讀取用戶的輸入,讀取後轉換流作為一個可讀流調用 pipe ,將用戶輸入的信息通過標準輸出寫到命令行窗口,此時 stdout 的作用是寫流。

總結

自定義流最常見的種類在上面都已經涵蓋了,真正的在開發中用到的不多,如果需要寫一個自定義流應該比上面的複雜很多,本文主要目的是認識什麼是自定義流,並瞭解寫一個自定義流的基本套路。


分享到:


相關文章: