我在閱讀NodeJS文件中讀出的19個套路
雖然我已經用了三年多的NodeJS,也曾經以為自己對其無所不知。但是我好像從未有安靜的坐下來仔細地閱讀NodeJS的完整文件。如果有熟悉我的朋友應該知道,我之前已經看了HTML,DOM,Web APIs,CSS,SVG以及ECMAScript的文件,NodeJS是我這個系列的最後一個待翻閱的山峰。在閱讀文件的過程中我也發現了很多本來不知道的知識,我覺得我有必要分享給大家。不過文件更多的是平鋪直敘,因此我也以閱讀的順序列舉出我覺得需要了解的點。
querystring:可以用作通用解析器的模組
很多時候我們會從資料庫或其他地方得到這種奇怪格式的字串:name:Sophie;shape:fox;condition:new
,一般來說我們會利用字串切割的方式來講字串劃分到JavaScript Object。不過querystring
也是個不錯的現成的工具:
const weirdoString = `name:Sophie;shape:fox;condition:new`; const result = querystring.parse(weirdoString, `;`, `:`); // result: // { // name: `Sophie`, // shape: `fox`, // condition: `new`, // };
V8 Inspector
以--inspect
引數執行你的Node應用程式,它會反饋你某個URL。將該URL複製到Chrome中並開啟,你就可以使用Chrome DevTools來除錯你的Node應用程式啦。詳細的實驗可以參考這篇文章。不過需要注意的是,該引數仍然屬於實驗性質。
nextTick 與 setImmediate的區別
這兩貨的區別可能光從名字上還看不出來,我覺得應該給它們取個別名:
process.nextTick()
應該為process.sendThisToTheStartOfTheQueue()
setImmediate
應該為sendThisToTheEndOfTheQueue()
再說句不相關的,React中的Props應該為stuffThatShouldStayTheSameIfTheUserRefreshes
,而State應該為stuffThatShouldBeForgottenIfTheUserRefreshes
。
Server.listen 可以使用Object作為引數
我更喜歡命名引數的方式呼叫函式,這樣相較於僅按照順序的無命名引數法會更直觀。別忘了Server.listen也可以使用某個Object作為引數:
require(`http`) .createServer() .listen({ port: 8080, host: `localhost`, }) .on(`request`, (req, res) => { res.end(`Hello World!`); });
不過這個特性不是表述在http.Server這個API中,而是在其父級net.Server的文件中。
相對地址
你傳入fs
模組的距離可以是相對地址,即相對於process.cwd()
。估計有些人早就知道了,不過我之前一直以為是隻能使用絕對地址:
const fs = require(`fs`); const path = require(`path`); // why have I always done this... fs.readFile(path.join(__dirname, `myFile.txt`), (err, data) => { // do something }); // when I could just do this? fs.readFile(`./path/to/myFile.txt`, (err, data) => { // do something });
Path Parsing:路徑解析
之前我一直不知道的某個功能就是從某個檔名中解析出路徑,檔名,檔案擴充套件等等:
myFilePath = `/someDir/someFile.json`; path.parse(myFilePath).base === `someFile.json`; // true path.parse(myFilePath).name === `someFile`; // true path.parse(myFilePath).ext === `.json`; // true
Logging with colors
別忘了console.dir(obj,{colors:true})
能夠以不同的色彩列印出鍵與值,這一點會大大增加日誌的可讀性。
使用setInterval執行定時任務
我喜歡使用setInterval
來定期執行資料庫清理任務,不過預設情況下在存在setInterval
的時候NodeJS並不會退出,你可以使用如下的方法讓Node沉睡:
const dailyCleanup = setInterval(() => { cleanup(); }, 1000 * 60 * 60 * 24); dailyCleanup.unref();
Use Signal Constants
如果你嘗試在NodeJS中殺死某個程式,估計你用過如下語法:
process.kill(process.pid, `SIGTERM`);
這個沒啥問題,不過既然第二個引數同時能夠使用字串與整形變數,那麼還不如使用全域性變數呢:
process.kill(process.pid, os.constants.signals.SIGTERM);
IP Address Validation
NodeJS中含有內建的IP地址校驗工具,這一點可以免得你寫額外的正規表示式:
require(`net`).isIP(`10.0.0.1`) 返回 4 require(`net`).isIP(`cats`) 返回 0
os.EOF
不知道你有沒有手寫過行結束符,看上去可不漂亮啊。NodeJS內建了os.EOF
,其在Windows下是rn
,其他地方是n
,使用os.EOL能夠讓你的程式碼在不同的作業系統上保證一致性:
const fs = require(`fs`); // bad fs.readFile(`./myFile.txt`, `utf8`, (err, data) => { data.split(`\r\n`).forEach(line => { // do something }); }); // good const os = require(`os`); fs.readFile(`./myFile.txt`, `utf8`, (err, data) => { data.split(os.EOL).forEach(line => { // do something }); });
HTTP 狀態碼
NodeJS幫我們內建了HTTP狀態碼及其描述,也就是http.STATUS_CODES
,鍵為狀態值,值為描述:
你可以按照如下方法使用:
someResponse.code === 301; // true require(`http`).STATUS_CODES[someResponse.code] === `Moved Permanently`; // true
避免異常崩潰
有時候碰到如下這種導致服務端崩潰的情況還是挺無奈的:
const jsonData = getDataFromSomeApi(); // But oh no, bad data! const data = JSON.parse(jsonData); // Loud crashing noise.
我為了避免這種情況,在全域性加上了一個:
process.on(`uncaughtException`, console.error);
當然,這種辦法絕不是最佳實踐,如果是在大型專案中我還是會使用PM2,然後將所有可能崩潰的程式碼加入到try...catch
中。
Just this once()
除了on
方法,once
方法也適用於所有的EventEmitters,希望我不是最後才知道這個的:
server.once(`request`, (req, res) => res.end(`No more from me.`));
Custom Console
你可以使用new console.Console(standardOut,errorOut)
,然後設定自定義的輸出流。你可以選擇建立console將資料輸出到檔案或者Socket或者第三方中。
DNS lookup
某個年輕人告訴我,Node並不會快取DNS查詢資訊,因此你在使用URL之後要等個幾毫秒才能獲取到資料。不過其實你可以使用dns.lookup()
來快取資料:
dns.lookup(`www.myApi.com`, 4, (err, address) => { cacheThisForLater(address); });
fs 在不同OS上有一定差異
fs.stats()
返回的物件中的mode
屬性在Windows與其他作業系統中存在差異。fs.lchmod()
僅在macOS中有效。- 僅在Windows中支援呼叫
fs.symlink()
時使用type
引數。 - 僅僅在macOS與Windows中呼叫
fs.watch()
時傳入recursive
選項。 - 在Linux與Windows中
fs.watch()
的回撥可以傳入某個檔名 - 使用
fs.open()
以及a+
屬性開啟某個目錄時僅僅在FreeBSD以及Windows上起作用,在macOS以及Linux上則存在問題。 - 在Linux下以追加模式開啟某個檔案時,傳入到
fs.write()
的position
引數會被忽略。
net 模組差不多比http快上兩倍
筆者在文件中看到一些關於二者效能的討論,還特地執行了兩個伺服器來進行真實比較。結果來看http.Server
大概每秒可以接入3400個請求,而net.Server
可以接入大概5500個請求。
// This makes two connections, one to a tcp server, one to an http server (both in server.js) // It fires off a bunch of connections and times the response // Both send strings. const net = require(`net`); const http = require(`http`); function parseIncomingMessage(res) { return new Promise((resolve) => { let data = ``; res.on(`data`, (chunk) => { data += chunk; }); res.on(`end`, () => resolve(data)); }); } const testLimit = 5000; /* ------------------ */ /* -- NET client -- */ /* ------------------ */ function testNetClient() { const netTest = { startTime: process.hrtime(), responseCount: 0, testCount: 0, payloadData: { type: `millipede`, feet: 100, test: 0, }, }; function handleSocketConnect() { netTest.payloadData.test++; netTest.payloadData.feet++; const payload = JSON.stringify(netTest.payloadData); this.end(payload, `utf8`); } function handleSocketData() { netTest.responseCount++; if (netTest.responseCount === testLimit) { const hrDiff = process.hrtime(netTest.startTime); const elapsedTime = hrDiff[0] * 1e3 + hrDiff[1] / 1e6; const requestsPerSecond = (testLimit / (elapsedTime / 1000)).toLocaleString(); console.info(`net.Server handled an average of ${requestsPerSecond} requests per second.`); } } while (netTest.testCount < testLimit) { netTest.testCount++; const socket = net.connect(8888, handleSocketConnect); socket.on(`data`, handleSocketData); } } /* ------------------- */ /* -- HTTP client -- */ /* ------------------- */ function testHttpClient() { const httpTest = { startTime: process.hrtime(), responseCount: 0, testCount: 0, }; const payloadData = { type: `centipede`, feet: 100, test: 0, }; const options = { hostname: `localhost`, port: 8080, method: `POST`, headers: { 'Content-Type': `application/x-www-form-urlencoded`, }, }; function handleResponse(res) { parseIncomingMessage(res).then(() => { httpTest.responseCount++; if (httpTest.responseCount === testLimit) { const hrDiff = process.hrtime(httpTest.startTime); const elapsedTime = hrDiff[0] * 1e3 + hrDiff[1] / 1e6; const requestsPerSecond = (testLimit / (elapsedTime / 1000)).toLocaleString(); console.info(`http.Server handled an average of ${requestsPerSecond} requests per second.`); } }); } while (httpTest.testCount < testLimit) { httpTest.testCount++; payloadData.test = httpTest.testCount; payloadData.feet++; const payload = JSON.stringify(payloadData); options[`Content-Length`] = Buffer.byteLength(payload); const req = http.request(options, handleResponse); req.end(payload); } } /* -- Start tests -- */ // flip these occasionally to ensure there's no bias based on order setTimeout(() => { console.info(`Starting testNetClient()`); testNetClient(); }, 50); setTimeout(() => { console.info(`Starting testHttpClient()`); testHttpClient(); }, 2000);
// This sets up two servers. A TCP and an HTTP one. // For each response, it parses the received string as JSON, converts that object and returns a string const net = require(`net`); const http = require(`http`); function renderAnimalString(jsonString) { const data = JSON.parse(jsonString); return `${data.test}: your are a ${data.type} and you have ${data.feet} feet.`; } /* ------------------ */ /* -- NET server -- */ /* ------------------ */ net .createServer((socket) => { socket.on(`data`, (jsonString) => { socket.end(renderAnimalString(jsonString)); }); }) .listen(8888); /* ------------------- */ /* -- HTTP server -- */ /* ------------------- */ function parseIncomingMessage(res) { return new Promise((resolve) => { let data = ``; res.on(`data`, (chunk) => { data += chunk; }); res.on(`end`, () => resolve(data)); }); } http .createServer() .listen(8080) .on(`request`, (req, res) => { parseIncomingMessage(req).then((jsonString) => { res.end(renderAnimalString(jsonString)); }); });
REPL tricks
- 如果你是在REPL模式下,就是直接輸入node然後進入互動狀態的模式。你可以直接輸入
.load someFile.js
然後可以載入包含自定義常量的檔案。 - 可以通過設定
NODE_REPL_HISTORY=""
來避免將日誌寫入到檔案中。 _
用來記錄最後一個計算值。- 在REPL啟動之後,所有的模組都已經直接載入成功。可以使用
os.arch()
而不是require(
os).arch()
來使用。
相關文章
- Oracle文件閱讀指南Oracle
- SUMO文件閱讀——PlainXMLAIXML
- Laravel 文件閱讀:雜湊Laravel
- Laravel 文件閱讀:授權Laravel
- Laravel 文件閱讀:認證Laravel
- 蘋果官方文件閱讀指南蘋果
- [譯] 我在閱讀 MDN 時發現的 3 個 Input 元素屬性
- go package官方文件閱讀方式GoPackage
- Laravel 文件閱讀:國際化Laravel
- Laravel 文件閱讀:Blade 模版Laravel
- Laravel 文件閱讀:控制器Laravel
- Laravel 文件閱讀:中介軟體Laravel
- Laravel 文件閱讀: HTTP 響應LaravelHTTP
- Laravel 文件閱讀:Eloquent 起步(下篇)Laravel
- iOS PropertyList 文件閱讀記錄iOS
- Oracle 官方文件閱讀順序Oracle
- Kafka文件閱讀筆記(一)Kafka筆記
- 在 Linux 終端中閱讀 RedditLinux
- 我們的『閱讀理解』出了錯
- Spark文件閱讀之一:Spark OverviewSparkView
- React Router文件閱讀筆記(上)React筆記
- [譯] 如何閱讀蘋果開發文件蘋果
- Laravel 文件閱讀:資料庫起步Laravel資料庫
- 淺讀-《深入淺出Nodejs》NodeJS
- [論文閱讀] RNN 在阿里DIEN中的應用RNN阿里
- ?Oracle文件閱讀指南-10gr2Oracle
- SpringBoot文件之Profiles的閱讀筆記Spring Boot筆記
- SpringBoot文件之IO的閱讀筆記Spring Boot筆記
- SpringBoot文件之Web的閱讀筆記Spring BootWeb筆記
- 閱讀jQuery原始碼帶給我們的18個驚喜jQuery原始碼
- 第三章:閱讀的第一個層次:閱讀的基礎——《如何閱讀一本書》
- 《Effective DevOps》閱讀筆記 19dev筆記
- 我是如何閱讀程式設計書的程式設計
- 教你如何閱讀Oracle資料庫官方文件Oracle資料庫
- Laravel 文件閱讀:用 Laravel Mix 編譯資產Laravel編譯
- 我也來打造一個個人閱讀追蹤系統
- 《例項化需求》閱讀筆記(3)-活的文件筆記
- SpringBoot文件之入門的閱讀筆記Spring Boot筆記