對於大部分有後端經驗的的同窗來講 Stream 對象是個再合理而常見的對象,但對於前端同窗 Stream 並非那麼理所固然,github 上甚至有一篇 9000 多 Star 的文章介紹到底什麼是 Stream —— stream-handbook。爲了更好的理解 Stream,在這篇文章的基礎上簡單總結歸納一下。javascript
在 Unix 系統中流就是一個很常見也很重要的概念,從術語上講流是對輸入輸出設備的抽象。html
ls | grep *.js
相似這樣的代碼咱們在寫腳本的時候常常能夠遇到,使用 |
鏈接兩條命令,把前一個命令的結果做爲後一個命令的參數傳入,這樣數據像是水流在管道中傳遞,每一個命令相似一個處理器,對數據作一些加工,所以 | 被稱爲 「管道符號」。前端
從程序角度而言流是有方向的數據,按照流動方向能夠分爲三種流java
NodeJS 關於流的操做被封裝到了 Stream 模塊,這個模塊也被多個核心模塊所引用。按照 Unix 的哲學:一切皆文件,在 NodeJS 中對文件的處理多數使用流來完成node
有一個很容易忽略的知識點:在 NodeJS 中全部的 Stream 都是 EventEmitter 的實例。git
咱們寫程序突然須要讀取某個配置文件 config.json,這時候簡單分析一下github
咱們應該使用 readable 流來作此事shell
const fs = require('fs'); const FILEPATH = '...'; const rs = fs.createReadStream(FILEPATH);
經過 fs 模塊提供的 createReadStream()
方法咱們輕鬆的建立了一個可讀的流,這時候 config.json 的內容從設備流向程序。咱們並無直接使用 Stream 模塊,由於 fs 內部已經引用了 Stream 模塊,並作了封裝。json
有了數據後咱們須要處理,好比須要寫到某個路徑 DEST ,這時候咱們遍須要一個 writable 的流,讓數據從程序流向設備。後端
const ws = fs.createWriteStream(DEST);
兩種流都有了,也就是兩個數據加工器,那麼咱們如何經過相似 Unix 的管道符號 |
來連接流呢?在 NodeJS 中管道符號就是 pipe()
方法。
const fs = require('fs'); const FILEPATH = '...'; const rs = fs.createReadStream(FILEPATH); const ws = fs.createWriteStream(DEST); rs.pipe(ws);
這樣咱們利用流實現了簡單的文件複製功能,關於 pipe() 方法的實現原理後面會提到,但有個值得注意地方:數據必須是從上游 pipe 到下游,也就是從一個 readable 流 pipe 到 writable 流。
上面提到了 readable 和 writable 的流,咱們稱之爲加工器,其實並不太恰當,由於咱們並無加工什麼,只是讀取數據,而後存儲數據。
若是有個需求,把本地一個 package.json 文件中的全部字母都改成小寫,並保存到同目錄下的 package-lower.json 文件下。
這時候咱們就須要用到雙向的流了,假定咱們有一個專門處理字符轉小寫的流 lower,那麼代碼寫出來大概是這樣的
const fs = require('fs'); const rs = fs.createReadStream('./package.json'); const ws = fs.createWriteStream('./package-lower.json'); rs.pipe(lower).pipe(ws);
這時候咱們能夠看出爲何稱 pipe() 鏈接的流爲加工器了,根據上面說的,必須從一個 readable 流 pipe 到 writable 流:
有點推理的趕腳呢,可以知足咱們需求的 lower 必須是雙向的流,具體使用 duplex 仍是 transform 後面咱們會提到。
固然若是咱們還有額外一些處理動做,好比字母還須要轉成 ASCII 碼,假定有一個流 ascii 那麼咱們代碼多是
rs.pipe(lower).pipe(acsii).pipe(ws);
一樣 ascii 也必須是雙向的流。這樣處理的邏輯是很是清晰的,那麼除了代碼清晰,使用流還有什麼好處呢?
有個用戶須要在線看視頻的場景,假定咱們經過 HTTP 請求返回給用戶電影內容,那麼代碼可能寫成這樣
const http = require('http'); const fs = require('fs'); http.createServer((req, res) => { fs.readFile(moviePath, (err, data) => { res.end(data); }); }).listen(8080);
這樣的代碼又兩個明顯的問題
用流能夠講電影文件一點點的放入內存中,而後一點點的返回給客戶(利用了 HTTP 協議的 Transfer-Encoding: chunked 分段傳輸特性),用戶體驗獲得優化,同時對內存的開銷明顯降低
const http = require('http'); const fs = require('fs'); http.createServer((req, res) => { fs.createReadStream(moviePath).pipe(res); }).listen(8080);
除了上述好處,代碼優雅了不少,拓展也比較簡單。好比須要對視頻內容壓縮,咱們能夠引入一個專門作此事的流,這個流不用關心其它部分作了什麼,只要是接入管道中就能夠了
const http = require('http'); const fs = require('fs'); const oppressor = require(oppressor); http.createServer((req, res) => { fs.createReadStream(moviePath) .pipe(oppressor) .pipe(res); }).listen(8080);
能夠看出來,使用流後,咱們的代碼邏輯變得相對獨立,可維護性也會有必定的改善,關於幾種流的具體使用方式且聽下回分解。