我在阅读 NodeJS 文档中读出的 19 个套路

wangqi1990 3年前
   <p>虽然我已经用了三年多的NodeJS,也曾经以为自己对其无所不知。但是我好像从未有安静的坐下来仔细地阅读NodeJS的完整文档。如果有熟悉我的朋友应该知道,我之前已经看了HTML,DOM,Web APIs,CSS,SVG以及ECMAScript的文档,NodeJS是我这个系列的最后一个待翻阅的山峰。在阅读文档的过程中我也发现了很多本来不知道的知识,我觉得我有必要分享给大家。不过文档更多的是平铺直叙,因此我也以阅读的顺序列举出我觉得需要了解的点。</p>    <h2><a href="/misc/goto?guid=4959724599738351569" rel="nofollow,noindex">querystring:可以用作通用解析器的模块</a></h2>    <p>很多时候我们会从数据库或其他地方得到这种奇怪格式的字符串: name:Sophie;shape:fox;condition:new ,一般来说我们会利用字符串切割的方式来讲字符串划分到JavaScript Object。不过 querystring 也是个不错的现成的工具:</p>    <pre>  <code class="language-javascript">const weirdoString = `name:Sophie;shape:fox;condition:new`;  const result = querystring.parse(weirdoString, `;`, `:`);  // result:  // {  //   name: `Sophie`,  //   shape: `fox`,  //   condition: `new`,  // };</code></pre>    <h2><a href="/misc/goto?guid=4959724599841998055" rel="nofollow,noindex">V8 Inspector</a></h2>    <p>以 --inspect 参数运行你的Node应用程序,它会反馈你某个URL。将该URL复制到Chrome中并打开,你就可以使用Chrome DevTools来调试你的Node应用程序啦。不过需要注意的是,该参数仍然属于实验性质。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/104dad96ecfbbe20ad689fb6be1c9ea6.png"></p>    <h2><a href="/misc/goto?guid=4959724599926697331" rel="nofollow,noindex">nextTick 与 setImmediate的区别</a></h2>    <p>这两货的区别可能光从名字上还看不出来,我觉得应该给它们取个别名:</p>    <ul>     <li> <p>process.nextTick() 应该为 process.sendThisToTheStartOfTheQueue()</p> </li>     <li> <p>setImmediate 应该为 sendThisToTheEndOfTheQueue()</p> </li>    </ul>    <p>再说句不相关的,React中的Props应该为 stuffThatShouldStayTheSameIfTheUserRefreshes ,而State应该为 stuffThatShouldBeForgottenIfTheUserRefreshes 。</p>    <h2><a href="/misc/goto?guid=4959724600024549663" rel="nofollow,noindex">Server.listen 可以使用Object作为参数</a></h2>    <p>我更喜欢命名参数的方式调用函数,这样相较于仅按照顺序的无命名参数法会更直观。别忘了Server.listen也可以使用某个Object作为参数:</p>    <pre>  <code class="language-javascript">require(`http`)    .createServer()    .listen({      port: 8080,      host: `localhost`,    })    .on(`request`, (req, res) => {      res.end(`Hello World!`);    });</code></pre>    <p>不过这个特性不是表述在 http.Server 这个API中,而是在其父级 net.Server 的文档中。</p>    <h2><a href="/misc/goto?guid=4959724600112229788" rel="nofollow,noindex">相对地址</a></h2>    <p>你传入 fs 模块的距离可以是相对地址,即相对于 process.cwd() 。估计有些人早就知道了,不过我之前一直以为是只能使用绝对地址:</p>    <pre>  <code class="language-javascript">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  });</code></pre>    <h2><a href="/misc/goto?guid=4959724600197671243" rel="nofollow,noindex">Path Parsing</a> :路径解析</h2>    <p>之前我一直不知道的某个功能就是从某个文件名中解析出路径,文件名,文件扩展等等:</p>    <pre>  <code class="language-javascript">myFilePath = `/someDir/someFile.json`;  path.parse(myFilePath).base === `someFile.json`; // true  path.parse(myFilePath).name === `someFile`; // true  path.parse(myFilePath).ext === `.json`; // true</code></pre>    <h2><a href="/misc/goto?guid=4959724600292402942" rel="nofollow,noindex">Logging with colors</a></h2>    <p>别忘了 console.dir(obj,{colors:true}) 能够以不同的色彩打印出键与值,这一点会大大增加日志的可读性。</p>    <h2><a href="/misc/goto?guid=4959724600387016615" rel="nofollow,noindex">使用setInterval执行定时任务</a></h2>    <p>我喜欢使用 setInterval 来定期执行数据库清理任务,不过默认情况下在存在 setInterval 的时候NodeJS并不会退出,你可以使用如下的方法让Node沉睡:</p>    <pre>  <code class="language-javascript">const dailyCleanup = setInterval(() => {    cleanup();  }, 1000 * 60 * 60 * 24);  dailyCleanup.unref();</code></pre>    <h2>Use Signal Constants</h2>    <p>如果你尝试在NodeJS中杀死某个进程,估计你用过如下语法:</p>    <pre>  <code class="language-javascript">process.kill(process.pid, `SIGTERM`);</code></pre>    <p>这个没啥问题,不过既然第二个参数同时能够使用字符串与整形变量,那么还不如使用全局变量呢:</p>    <pre>  <code class="language-javascript">process.kill(process.pid, os.constants.signals.SIGTERM);</code></pre>    <h2><a href="/misc/goto?guid=4959724600476273041" rel="nofollow,noindex">IP Address Validation</a></h2>    <p>NodeJS中含有内置的IP地址校验工具,这一点可以免得你写额外的正则表达式:</p>    <pre>  <code class="language-javascript">require(`net`).isIP(`10.0.0.1`) 返回 4  require(`net`).isIP(`cats`) 返回 0</code></pre>    <h2><a href="/misc/goto?guid=4959724600566174423" rel="nofollow,noindex">os.EOF</a></h2>    <p>不知道你有没有手写过行结束符,看上去可不漂亮啊。NodeJS内置了 os.EOF ,其在Windows下是 rn ,其他地方是 n , 使用os.EOL 能够让你的代码在不同的操作系统上保证一致性:</p>    <pre>  <code class="language-javascript">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    });  });</code></pre>    <h2><a href="/misc/goto?guid=4959724600650045134" rel="nofollow,noindex">HTTP 状态码</a></h2>    <p>NodeJS帮我们内置了HTTP状态码及其描述,也就是 http.STATUS_CODES ,键为状态值,值为描述:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/af85b395794584ce40ebb89b8ce7f101.png"></p>    <p>你可以按照如下方法使用:</p>    <pre>  <code class="language-javascript">someResponse.code === 301; // true  require(`http`).STATUS_CODES[someResponse.code] === `Moved Permanently`; // true</code></pre>    <h2><a href="/misc/goto?guid=4959724600742185674" rel="nofollow,noindex">避免异常崩溃</a></h2>    <p>有时候碰到如下这种导致服务端崩溃的情况还是挺无奈的:</p>    <pre>  <code class="language-javascript">const jsonData = getDataFromSomeApi(); // But oh no, bad data!  const data = JSON.parse(jsonData); // Loud crashing noise.</code></pre>    <p>我为了避免这种情况,在全局加上了一个:</p>    <pre>  <code class="language-javascript">process.on(`uncaughtException`, console.error);</code></pre>    <p>当然,这种办法绝不是 最佳实践 ,如果是在大型项目中我还是会使用 PM2 ,然后将所有可能崩溃的代码加入到 try...catch 中。</p>    <h2><a href="/misc/goto?guid=4959724600836418688" rel="nofollow,noindex">Just this once()</a></h2>    <p>除了 on 方法, once 方法也适用于所有的EventEmitters,希望我不是最后才知道这个的:</p>    <pre>  <code class="language-javascript">server.once(`request`, (req, res) => res.end(`No more from me.`));</code></pre>    <h2><a href="/misc/goto?guid=4959724600926721885" rel="nofollow,noindex">Custom Console</a></h2>    <p>你可以使用 new console.Console(standardOut,errorOut) ,然后设置自定义的输出流。你可以选择创建console将数据输出到文件或者Socket或者第三方中。</p>    <h2><a href="/misc/goto?guid=4959724601024161807" rel="nofollow,noindex">DNS lookup</a></h2>    <p>某个年轻人告诉我,Node 并不会缓存DNS查询信息 ,因此你在使用URL之后要等个几毫秒才能获取到数据。不过其实你可以使用 dns.lookup() 来缓存数据:</p>    <pre>  <code class="language-javascript">dns.lookup(`www.myApi.com`, 4, (err, address) => {    cacheThisForLater(address);  });</code></pre>    <h2><a href="/misc/goto?guid=4959649732599974302" rel="nofollow,noindex">fs 在不同OS上有一定差异</a></h2>    <ul>     <li> <p>fs.stats() 返回的对象中的 mode 属性在Windows与其他操作系统中存在差异。</p> </li>     <li> <p>fs.lchmod() 仅在macOS中有效。</p> </li>     <li> <p>仅在Windows中支持调用 fs.symlink() 时使用 type 参数。</p> </li>     <li> <p>仅仅在macOS与Windows中调用 fs.watch() 时传入 recursive 选项。</p> </li>     <li> <p>在Linux与Windows中 fs.watch() 的回调可以传入某个文件名</p> </li>     <li> <p>使用 fs.open() 以及 a+ 属性打开某个目录时仅仅在FreeBSD以及Windows上起作用,在macOS以及Linux上则存在问题。</p> </li>     <li> <p>在Linux下以追加模式打开某个文件时,传入到 fs.write() 的 position 参数会被忽略。</p> </li>    </ul>    <h2><a href="/misc/goto?guid=4959724601144266872" rel="nofollow,noindex">net 模块差不多比http快上两倍</a></h2>    <p>笔者在文档中看到一些关于二者性能的讨论,还特地运行了两个服务器来进行真实比较。结果来看 http.Server 大概每秒可以接入3400个请求,而 net.Server 可以接入大概5500个请求。</p>    <pre>  <code class="language-javascript">// 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);</code></pre>    <pre>  <code class="language-javascript">// 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));      });    });</code></pre>    <h2><a href="/misc/goto?guid=4959724601233199109" rel="nofollow,noindex">REPL tricks</a></h2>    <ul>     <li> <p>如果你是在REPL模式下,就是直接输入node然后进入交互状态的模式。你可以直接输入 .load someFile.js 然后可以载入包含自定义常量的文件。</p> </li>     <li> <p>可以通过设置 NODE_REPL_HISTORY="" 来避免将日志写入到文件中。</p> </li>     <li> <p>_ 用来记录最后一个计算值。</p> </li>     <li> <p>在REPL启动之后,所有的模块都已经直接加载成功。可以使用 os.arch() 而不是 require( os ).arch() 来使用。</p> </li>    </ul>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000007435273</p>    <p> </p>