当前位置: 首页 > 后端技术 > Node.js

深入Node.js流程及子流程:从文档到实践

时间:2023-04-04 01:37:20 Node.js

欢迎来到Github仓库,这是一个从2018年开始持续更新的前端&算法开源博客,目前有很多node学习、js面试笔记、css3动画设计、webpack4系列教程、设计模式、剑指offer·js版等系列。仓库地址:https://github.com/donongyuanxin/blog流程:流程模块流程module是nodejs提供给开发者与当前进程交互的工具,它提供了很多有用的API。从文档入手,窥豹一斑,进一步了解和学习进程模块:如何处理命令参数?如何处理工作目录?如何处理异常?如何处理进程退出?Process的标准流对象深入理解process.nextTick如何处理命令参数?命令行参数指的是两个方面:传递给node的参数。比如node--harmonyscript.js--version中,--harmony是传递给node进程的参数。比如nodescript.js中的--version--help,--version--help就是传递给进程的参数。分别通过process.argv和process.execArgv获得。如何处理工作目录?当前工作目录可以通过process.cwd()获取。通过process.chdir(directory)可以切换当前工作目录,失败后会抛出异常。做法如下:functionsafeChdir(dir){try{process.chdir(dir);返回真;}赶上(错误){返回错误;}}如何处理异常?uncaughtException事件Nodejs可以通过try-catch捕获异常。如果异常没有被捕获,它会一直从底部冒泡到事件循环。如果不处理冒泡到事件循环的异常,会导致当前进程异常退出。根据文档,可以通过监听进程的uncaughtException事件来处理未捕获的异常:process.on("uncaughtException",(err,origin)=>{console.log(err.message);});consta=1/b;console.log("abc");//上面的代码不会执行,控制台输出:bisnotdefined。错误信息被捕获,进程以0退出。开发者可以在uncaughtException事件中清除一些分配的资源(文件描述符、句柄等),不建议重启进程??。unhandledRejection事件如果Promise回调的异常没有被.catch()捕获到,就会触发进程的unhandledRejection事件:process.on("unhandledRejection",(err,promise)=>{console.log(err.消息);});Promise.reject(newError("错误信息"));//未被catch捕获的异常将由unhandledRejection事件警告事件警告事件处理,这不是Node.js和Javascript错误处理过程的正式部分。当Node.js检测到可能导致应用程序性能问题、错误或安全风险的代码实践时,它可以发出警报。比如在上一段代码中,如果出现未捕获的promise回调异常,就会触发warning事件。如何处理进程退出?process.exit()vsprocess.exitCode一个nodejs进程,可以通过process.exit()指定退出码直接退出。不建议直接使用process.exit(),这样会导致事件循环中的任务不能直接处理,可能会造成数据截断和丢失(比如写入stdout)。setTimeout(()=>{console.log("我不会执行");});process.exit(0);正确安全的处理方式是设置process.exitCode,让进程自然退出。setTimeout(()=>{console.log("我不会执行");});process.exitCode=1;beforeExit事件用于处理进程退出的事件有:beforeExit事件和exit事件。当Node.js清空其事件循环并且没有更多工作要安排时,会触发beforeExit事件。比如退出前需要一些异步操作,可以写在beforeExit事件中:lethasSend=false;process.on("beforeExit",()=>{if(hasSend)return;//避免死循环setTimeout(()=>{console.log("模拟发送数据服务");hasSend=true;},500);});console.log("......");//输出://。......//mocksenddatatoserve注意:如果是beforeExit事件中的异步任务,会重新加入任务队列。此时任务队列完成所有任务后,再次触发beforeExit事件。因此,如果不处理,可能会出现死循环。如果显式调用exit()则不会触发此事件。退出事件在退出事件中,只能进行同步操作。调用“exit”事件侦听器后,Node.js进程将立即退出,导致仍在事件循环中排队的任何其他工作被放弃。流程流程的标准流对象提供了3个标准流。请注意,其中一些在某些时候会同步阻塞(请参阅文档)。process.stderr:WriteStream类型,console.error的底层实现,默认对应screenprocess.stdout:WriteStream类型,console.log的底层实现,默认对应screenprocess.stdin:ReadStream类型,默认对应键盘输入下面是基于“productionReader-consumermodel”及时读取控制台输入输出的代码:process.stdin.setEncoding("utf8");process.stdin.on("readable",()=>{letchunk;while((chunk=process.stdin.read())!==null){process.stdout.write(`>>>${chunk}`);}});process.stdin.on("结束",()=>{process.stdout.write("End");});事件的含义可以参考stream的文档。深入理解process.nextTick第一次看到process.nextTick的时候,还挺懵的。我从文档中可以看出它的目的是将回调函数作为微任务,放入事件循环的任务队列中。但这样做的意义何在?因为nodejs不适合计算密集型应用,一个进程就是一个线程,当前时间点只有一个事件在执行。所以,如果我们的事件占用了很多cpu时间,后面的事件就得等很长时间。所以nodejs的一个编程原则就是尽量缩短每个事件的执行时间。process.nextTick的作用就在这里,把一个大任务分解成多个小任务。示例代码如下://拆分成2个函数执行functionBigThing(){doPartThing();process.nextTick(()=>finishThing());}在事件循环中,nextTick注册的任务什么时候执行??请看下面的代码:setTimeout(function(){console.log("First1second");process.nextTick(function(){console.log("First1second:nextTick");});},1000);setTimeout(function(){console.log("第二个1秒");},1000);console.log("我要输出1");process.nextTick(function(){console.log(“nextTick”);});console.log("我要输出2");输出结果如下,nextTick早于setTimeout:我要输出1我要输出2nextTickfirst1秒first1秒:nextTick的第二个1秒在浏览器端,nextTick会退化为setTimeout(callback,0).但是请在nodejs中使用nextTick而不是setTimeout,前者效率更高,而且严格来说两者创建的事件在任务队列中的顺序不一样(见前面的代码)。子进程:child_process模块??掌握nodejs的child_process模块??可以大大提升nodejs的开发能力,比如主从进程优化CPU计算,多进程开发等等。本文从以下几个方面介绍child_process模块??的使用:创建子进程父子进程通信独立子进程processpipeline创建子进程nodejs的child_process模块??创建子进程:spawn,fork,exec,execFile。它们的关系如下:fork、exec、execFile都是通过spawn实现的。exec默认创建一个shell。execFile默认不创建shell,这意味着不能使用I/O重定向和文件glob,但效率更高。spawn、exec、execFile都有同步版本,可能会造成进程阻塞。child_process.spawn()的使用:const{spawn}=require("child_process");//返回ChildProcess对象,默认其上的stdio不为nullconstls=spawn("ls",["-lh"]);ls.stdout.on("data",data=>{console.log(`stdout:${data}`);});ls.stderr.on("data",data=>{控制台.error(`stderr:${data}`);});ls.on("close",code=>{console.log(`childprocessexited,exitcode${code}`);});child_process.exec()usage:const{exec}=require("child_process");//通过回调函数操作stdioexec("ls-lh",(err,stdout,stderr)=>{if(err){console.error(`执行错误:${err}`);返回;}console.log(`stdout:${stdout}`);console.error(`stderr:${stderr}`);});父子进程通信fork()返回的ChildProcess对象监听其上的message事件接收子进程消息;调用send方法实现IPC。parent.js代码如下:const{fork}=require("child_process");constcp=fork("./sub.js");cp.on("message",msg=>{console.log("父进程收到消息:",msg);});cp.send("我是父进程");sub.js代码如下:process.on("message",m=>{console.log("subprocessReceivedmessage:",m);});process.send("我是子进程");运行后结果:父进程收到消息:我是子进程子进程收到消息:我是父进程的独立子进程一般情况下,父进程必须等待子进程退出后才可以退出。如果您希望父进程先退出而不受子进程的影响,那么您应该:在ChildProcess对象上调用unref()。options.detached设置为true。子进程的stdio不能连接到父进程main.js代码如下:const{spawn}=require("child_process");constsubprocess=spawn(process.argv0,["sub.js"],{detached:true,stdio:"ignore"});subprocess.unref();sub.js代码如下:setInterval(()=>{},1000);processpipelineoptions.stdio选项用于配置在父进程和子进程之间建立的管道。默认情况下,子进程的stdin、stdout和stderr被重定向到ChildProcess对象上相应的subprocess.stdin、subprocess.stdout和subprocess.stderr流。这意味着可以在父进程中通过监听其上的数据事件来获取子进程的I/O。可用于实现“重定向”:constfs=require("fs");constchild_process=require("child_process");constsubprocess=child_process.spawn("ls",{stdio:[0,//使用父进程的stdin给子进程使用。"pipe",//将子进程的stdout管道到父进程.fs.openSync("err.out","w")//将子进程的stderr指向一个文档。]});也可用于实现“管道操作符”:const{spawn}=require("child_process");constps=spawn("ps",["ax"]);constgrep=spawn("grep",["ssh"]);ps.stdout.on("data",data=>{grep.stdin.write(data);});ps.stderr.on("数据",err=>{console.error(`psstderr:${err}`);});ps.on("close",code=>{if(code!==0){console.log(`ps进程退出,退出代码${code}`);}grep.stdin.end();});grep.stdout.on("data",data=>{console.log(data.toString());});grep.标准错误。on("data",data=>{console.error(`grepstderr:${data}`);});grep.on("close",code=>{if(code!==0){console.log(`grep进程退出,退出代码${code}`);}});参考链接Nodejsv12Stream文档Nodejsv12进程文档nodejs学习笔记一篇构建你的NodeJS知识体系的文章Node.js-Process学习笔记AdvancedglobNodejs:Howtoplaywiththerotorprocess(child_process)最后感觉还不错,请给点个赞,你的支持就是我最大的动力