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

blacksnake 发布于1年前 阅读9460次
0 条评论

 

本文翻译自David Gilbertson的[19-things-i-learnt-reading-the-nodejs-docs](

https://hackernoon.com/19-thi...

本文从属于笔者的 Web开发入门与最佳实践NodeJS入门与最佳实践 系列文章。

虽然我已经用了三年多的NodeJS,也曾经以为自己对其无所不知。但是我好像从未有安静的坐下来仔细地阅读NodeJS的完整文档。如果有熟悉我的朋友应该知道,我之前已经看了HTML,DOM,Web APIs,CSS,SVG以及ECMAScript的文档,NodeJS是我这个系列的最后一个待翻阅的山峰。在阅读文档的过程中我也发现了很多本来不知道的知识,我觉得我有必要分享给大家。不过文档更多的是平铺直叙,因此我也以阅读的顺序列举出我觉得需要了解的点。

querystring:可以用作通用解析器的模块

很多时候我们会从数据库或其他地方得到这种奇怪格式的字符串: name:Sophie;shape:fox;condition:new ,一般来说我们会利用字符串切割的方式来讲字符串划分到JavaScript Object。不过 querystring 也是个不错的现成的工具:

constweirdoString=`name:Sophie;shape:fox;condition:new`;constresult=querystring.parse(weirdoString,`;`,`:`);//result://{//name:`Sophie`,//shape:`fox`,//condition:`new`,//};

V8 Inspector

以 --inspect 参数运行你的Node应用程序,它会反馈你某个URL。将该URL复制到Chrome中并打开,你就可以使用Chrome DevTools来调试你的Node应用程序啦。详细的实验可以参考 这篇文章 。不过需要注意的是,该参数仍然属于实验性质。

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

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(`HelloWorld!`);});

不过这个特性不是表述在 http.Server 这个API中,而是在其父级 net.Server 的文档中。

相对地址

你传入 fs 模块的距离可以是相对地址,即相对于 process.cwd() 。估计有些人早就知道了,不过我之前一直以为是只能使用绝对地址:

constfs=require(`fs`);constpath=require(`path`);//whyhaveIalwaysdonethis...fs.readFile(path.join(__dirname,`myFile.txt`),(err,data)=>{//dosomething});//whenIcouldjustdothis?fs.readFile(`./path/to/myFile.txt`,(err,data)=>{//dosomething});

Path Parsing :路径解析

之前我一直不知道的某个功能就是从某个文件名中解析出路径,文件名,文件扩展等等:

myFilePath=`/someDir/someFile.json`;path.parse(myFilePath).base===`someFile.json`;//truepath.parse(myFilePath).name===`someFile`;//truepath.parse(myFilePath).ext===`.json`;//true

Logging with colors

别忘了 console.dir(obj,{colors:true}) 能够以不同的色彩打印出键与值,这一点会大大增加日志的可读性。

使用setInterval执行定时任务

我喜欢使用 setInterval 来定期执行数据库清理任务,不过默认情况下在存在 setInterval 的时候NodeJS并不会退出,你可以使用如下的方法让Node沉睡:

constdailyCleanup=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`)返回4require(`net`).isIP(`cats`)返回0

os.EOF

不知道你有没有手写过行结束符,看上去可不漂亮啊。NodeJS内置了 os.EOF ,其在Windows下是 rn ,其他地方是 n , 使用os.EOL 能够让你的代码在不同的操作系统上保证一致性:

constfs=require(`fs`);//badfs.readFile(`./myFile.txt`,`utf8`,(err,data)=>{data.split(`\r\n`).forEach(line=>{//dosomething});});//goodconstos=require(`os`);fs.readFile(`./myFile.txt`,`utf8`,(err,data)=>{data.split(os.EOL).forEach(line=>{//dosomething});});

HTTP 状态码

NodeJS帮我们内置了HTTP状态码及其描述,也就是 http.STATUS_CODES ,键为状态值,值为描述:

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

你可以按照如下方法使用:

someResponse.code===301;//truerequire(`http`).STATUS_CODES[someResponse.code]===`MovedPermanently`;//true

避免异常崩溃

有时候碰到如下这种导致服务端崩溃的情况还是挺无奈的:

constjsonData=getDataFromSomeApi();//Butohno,baddata!constdata=JSON.parse(jsonData);//Loudcrashingnoise.

我为了避免这种情况,在全局加上了一个:

process.on(`uncaughtException`,console.error);

当然,这种办法绝不是 最佳实践 ,如果是在大型项目中我还是会使用 PM2 ,然后将所有可能崩溃的代码加入到 try...catch 中。

Just this once()

除了 on 方法, once 方法也适用于所有的EventEmitters,希望我不是最后才知道这个的:

server.once(`request`,(req,res)=>res.end(`Nomorefromme.`));

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个请求。

//Thismakestwoconnections,onetoatcpserver,onetoanhttpserver(bothinserver.js)//Itfiresoffabunchofconnectionsandtimestheresponse//Bothsendstrings.constnet=require(`net`);consthttp=require(`http`);functionparseIncomingMessage(res){returnnewPromise((resolve)=>{letdata=``;res.on(`data`,(chunk)=>{data+=chunk;});res.on(`end`,()=>resolve(data));});}consttestLimit=5000;/*------------------*//*--NETclient--*//*------------------*/functiontestNetClient(){constnetTest={startTime:process.hrtime(),responseCount:0,testCount:0,payloadData:{type:`millipede`,feet:100,test:0,},};functionhandleSocketConnect(){netTest.payloadData.test++;netTest.payloadData.feet++;constpayload=JSON.stringify(netTest.payloadData);this.end(payload,`utf8`);}functionhandleSocketData(){netTest.responseCount++;if(netTest.responseCount===testLimit){consthrDiff=process.hrtime(netTest.startTime);constelapsedTime=hrDiff[0]*1e3+hrDiff[1]/1e6;constrequestsPerSecond=(testLimit/(elapsedTime/1000)).toLocaleString();console.info(`net.Serverhandledanaverageof${requestsPerSecond}requestspersecond.`);}}while(netTest.testCount<testLimit){netTest.testCount++;constsocket=net.connect(8888,handleSocketConnect);socket.on(`data`,handleSocketData);}}/*-------------------*//*--HTTPclient--*//*-------------------*/functiontestHttpClient(){consthttpTest={startTime:process.hrtime(),responseCount:0,testCount:0,};constpayloadData={type:`centipede`,feet:100,test:0,};constoptions={hostname:`localhost`,port:8080,method:`POST`,headers:{'Content-Type':`application/x-www-form-urlencoded`,},};functionhandleResponse(res){parseIncomingMessage(res).then(()=>{httpTest.responseCount++;if(httpTest.responseCount===testLimit){consthrDiff=process.hrtime(httpTest.startTime);constelapsedTime=hrDiff[0]*1e3+hrDiff[1]/1e6;constrequestsPerSecond=(testLimit/(elapsedTime/1000)).toLocaleString();console.info(`http.Serverhandledanaverageof${requestsPerSecond}requestspersecond.`);}});}while(httpTest.testCount<testLimit){httpTest.testCount++;payloadData.test=httpTest.testCount;payloadData.feet++;constpayload=JSON.stringify(payloadData);options[`Content-Length`]=Buffer.byteLength(payload);constreq=http.request(options,handleResponse);req.end(payload);}}/*--Starttests--*///fliptheseoccasionallytoensurethere'snobiasbasedonordersetTimeout(()=>{console.info(`StartingtestNetClient()`);testNetClient();},50);setTimeout(()=>{console.info(`StartingtestHttpClient()`);testHttpClient();},2000);
//Thissetsuptwoservers.ATCPandanHTTPone.//Foreachresponse,itparsesthereceivedstringasJSON,convertsthatobjectandreturnsastringconstnet=require(`net`);consthttp=require(`http`);functionrenderAnimalString(jsonString){constdata=JSON.parse(jsonString);return`${data.test}:yourarea${data.type}andyouhave${data.feet}feet.`;}/*------------------*//*--NETserver--*//*------------------*/net.createServer((socket)=>{socket.on(`data`,(jsonString)=>{socket.end(renderAnimalString(jsonString));});}).listen(8888);/*-------------------*//*--HTTPserver--*//*-------------------*/functionparseIncomingMessage(res){returnnewPromise((resolve)=>{letdata=``;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() 来使用。

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。