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("讀取完成"); }); // <Buffer 31> // <Buffer 32> // <Buffer 33> // 讀取完成複製程式碼
實現一個自定義可寫流 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); // <Buffer 31> ---- // <Buffer 32> xxxx // <Buffer 32> ---- // <Buffer 33> 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
的作用是寫流。
總結
自定義流最常見的種類在上面都已經涵蓋了,真正的在開發中用到的不多,如果需要寫一個自定義流應該比上面的複雜很多,本文主要目的是認識什麼是自定義流,並瞭解寫一個自定義流的基本套路。