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

Node.js指南(领域模块剖析)

时间:2023-04-03 23:25:30 Node.js

领域模块可用性问题剖析隐式行为开发者可以创建新的领域,然后简单地运行domain.enter(),然后,它充当无法观察的未来投掷者any异常的通用捕获器允许模块作者拦截不同模块中不相关代码的异常,防止代码的始作俑者知道自己的异常。这是一个间接链接的模块如何影响另一个模块的示例://modulea.jsconstb=require('./b');constc=require('./c');//moduleb.jsconstd=require('domain').create();d.on('error',()=>{/*silenceeverything*/});d.enter();//模块c.jsconstdep=require('some-dep');dep.method();//哦哦!这种方法实际上并不存在。由于模块b进入域但从未退出,任何未捕获的异常都将被吞噬,不让模块c知道为什么它没有运行整个脚本,留下可能部分填充的module.exports。这样做不同于侦听“uncaughtException”,因为后者明确意味着全局捕获错误,另一个问题是域在任何“uncaughtException”处理程序之前被处理,并阻止它们运行。另一个问题是,如果事件发射器上没有设置“错误”处理程序,域会自动路由错误,对此没有可选的插入机制,而是自动传播到整个异步链。这可能看起来很有用,但是一旦异步调用是两个或多个模块深度,其中一个不包含错误处理程序,域的创建者会突然捕获意外异常,而抛出者的异常将被作者忽略.下面是一个简单的示例,说明缺少“错误”处理程序如何允许活动域拦截错误:constdomain=require('domain');constnet=require('net');constd=domain.create();d.on('error',(err)=>console.error(err.message));d.run(()=>net.createServer((c)=>{c.end();c.write('bye');}).listen(8000));即使通过d.remove(c)手动删除连接也不会阻止连接的错误被自动拦截。困扰错误路由和异常处理的一个失败是错误冒泡的不一致,下面是一个示例,说明嵌套域如何不根据异常发生的时间来冒泡异常:constdomain=require('domain');constnet=require('net');constd=domain.create();d.on('error',()=>console.error('d截获错误'));d.run(()=>{constserver=net.createServer((c)=>{conste=domain.create();//没有设置“错误”处理程序。e.run(()=>{//这不会是被d的错误处理程序捕获。setImmediate(()=>{thrownewError('thrownfromsetImmediate');});//虽然这个会冒泡到d的错误处理程序。thrownewError('immediatelythrown');});}).听(8080);});可以预期嵌套域始终保持嵌套状态,并始终将异常传播到域堆栈中,或者异常永远不会自行冒泡,不幸的是,这两种情况都可能发生,从而导致潜在的混乱行为,甚至可能难以调试时序违规。API差距虽然基于使用EventEmitter的API可以使用bind()并且errback样式的回调可以使用intercept(),但隐式绑定到活动域的替代API必须在run()内部执行。这意味着如果模块作者想要使用上述机制的替代方案来支持域,他们必须自己手动实现域支持,而不是能够利用现有的隐式机制。错误传播如果可能的话,跨嵌套域传播错误并不简单,现有文档显示了一个简单示例,说明如果请求处理程序中存在错误,如何关闭()http服务器,但它没有解释的是如果请求处理程序isAnotherasynchronousrequesttocreateanotherdomaininstance,如何关闭服务器,用下面一个错误传播失败的简单例子:constd1=domain.create();d1.foo=true;//自定义成员以在consoled1中更加可见。on('error',(er)=>{/*处理错误*/});d1.run(()=>setTimeout(()=>{constd2=domain.create();d2.bar=43;d2.on('error',(er)=>console.error(er.message,domain._stack));d2.run(()=>{setTimeout(()=>{setTimeout(()=>{thrownewError('outer');});thrownewError('inner');});});}));即使域实例用于本地存储,仍然可以访问资源。没有办法让错误继续从d2传播回d1。快速检查可能会告诉我们,简单地从d2的域“错误”处理程序中抛出将允许d1然后捕获异常并执行它自己的错误处理程序,尽管事实并非如此,在检查域之后。_stack你会看到堆栈只包含d2.这可能被认为是API的失败,但即使它确实以这种方式运行,仍然存在传递异??步执行中的分支失败的事实的问题,并且该分支中的所有进一步操作都必须停止。在http请求处理程序的示例中,如果我们触发多个异步请求,每个请求都将write()的数据发送回客户端,尝试将write()发送到已关闭的句柄将产生更多错误,异常资源清理。以下脚本包含一个更复杂的示例,如果在给定连接或其任何依赖项中发生异常,则在小型资源依赖项树中进行适当清理,将脚本分解为基本操作:'usestrict';constdomain=require('domain');constEE=require('events');constfs=require('fs');constnet=require('net');constutil=require('util');constprint=process._rawDebug;constpipeList=[];constFILENAME='/tmp/tmp.tmp';constPIPENAME='/tmp/node-domain-example-';constFILESIZE=1024;letuid=0;//设置临时资源constbuf=Buffer.alloc(FILESIZE);for(leti=0;i{this._alive=false;});}util.inherits(ConnectionResource,EE);ConnectionResource.prototype.end=functionend(chunk){this._alive=false;这个._connection.end(块);this.emit('end');};ConnectionResource.prototype.isAlive=functionisAlive(){returnthis._alive;};ConnectionResource.prototype.id=functionid(){returnthis._id;};ConnectionResource.prototype.write=functionwrite(chunk){this.emit('data',chunk);returnthis._connection.write(chunk);};//示例beginnet.createServer((c)=>{constcr=newConnectionResource(c);constd1=domain.create();fs.open(FILENAME,'r',d1.intercept((fd)=>{streamInParts(fd,cr,0);}));pipeData(cr);c.on('close',()=>cr.end());}).listen(8080);functionstreamInParts(fd,cr,pos){constd2=domain.create();常量活着=真;d2.on('error',(er)=>{print('d2error:',er.message);cr.end();});fs.read(fd,Buffer.alloc(10),0,10,pos,d2.intercept((bRead,buf)=>{if(!cr.isAlive()){返回fs.close(fd);}if(cr._connection.bytesWrittenstreamInParts(fd,cr,pos+bRead),1000);}else{cr._connection.once('drain',()=>streamInParts(fd,cr,pos+bRead));}返回;}cr.end(buf);fs.close(fd);}));}functionpipeData(cr){constpname=PIPENAME+cr.id();constps=net.createServer();constd3=domain.create();const连接列表=[];d3.on('error',(er)=>{print('d3error:',er.message);cr.end();});d3.add(ps);ps.on('connection',(conn)=>{connectionList.push(conn);conn.on('data',()=>{});//不关心传入的数据。conn.on('close',()=>{connectionList.splice(connectionList.indexOf(conn),1);});});cr.on('data',(chunk)=>{for(leti=0;i{for(leti=0;iprocess.exit());process.on('exit',()=>{try{for(leti=0;i{//使用域在//连接中跨事件传播数据,这样我们就不必在任何地方传递参数//constd=domain.create();d.data={connection:c};d.add(c);//执行一些无用的异步数据转换的模拟类//用于演示目的。constds=newDataStream(dataTransformed);c.on('data',(chunk)=>ds.data(chunk));}).listen(8080,()=>console.log('listeningon8080'));functiondataTransformed(chunk){//失败!因为DataStream实例还创建了一个//域,我们现在失去了//希望使用的活动域。domain.active.data.connection.write(chunk);}functionDataStream(cb){this.cb=cb;//DataStream也想使用域进行数据传播!//不幸的是,这将与//已经存在的任何域冲突。this.domain=domain.create();this.domain.data={inst:this};}DataStream.prototype.data=functiondata(chunk){//这段代码是自包含的,但假装它是一个复杂的//至少跨越一个其他模块的操作。所以//传递“this”等并不容易。this.domain.run(()=>{//模拟执行数据的异步操作transform.setImmediate(()=>{for(leti=0;i