Node.js domain模块处理错误的问题操作
1、domain模块概述
众所周知,在JavaScript脚本代码中,虽然可以使用try…catch机制来捕捉同步方法中抛出的错误,但是不能使用try…catch机制来捕捉异步方法中抛出的错误。
例如,由于在下面这段代码中使用了同步模式的readFileSync方法读取文件,因此可以使用try…catch机制来捕捉在执行该方法时抛出的错误。
varfs=require('fs');
try{
var data=fs.readFileSync('test.txt','utf8');
//在控制台中输出文件内容
console.log(data);
}
catch(ex){
console.log('读取文件时发生错误。');
}现在查看如下所示的代码。在这段代码中,创建了一个HTTP服务器,该服务器在接收到客户端请求后,向客户端返回“你好”字符串。在用于创建HTTP服务器的createServer方法所使用的callback参数值回调函数中,将首先调用一个并不存在的方法noneexist,这必然会导致回调函数中抛出一个错误,假如我们采用try…catch机制,将捕捉不到该错误。
var http = require('http');
try{
http.createServer(function (req, res) {
if(req.url!=="/favicon.ico"){
noneexist();
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<head><meta charset="utf-8"/></head>');
res.end('你好\n');
}
}).listen(1337, "127.0.0.1");
}
catch(err){
console.log('接收客户端请求时发生以下错误:');
console.log(err.code);
}将这段代码保存在应用程序根目录下的app.js文件中,然后在命令行窗口中运行该文件,接着在浏览器的地址栏中输入“http://localhost:1337”,此时,命令行窗口中将显示一个错误被触发,应用程序也将由于该错误的触发而导致关闭。

修改上述代码,在该示例代码中,改为使用uncaughtException事件来捕获任何未被处理的错误。
var http = require('http');
http.createServer(function (req, res) {
if(req.url!=="/favicon.ico"){
noneexist();
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<head><meta charset="utf-8"/></head>');
res.end('你好\n');
}
}).listen(1337, "127.0.0.1");
process.on('uncaughtException', function(err) {
console.log('接收客户端请求时发生以下错误:');
console.log(err);
});浏览器的地址栏中输入“http://localhost:1337”,命令行窗口中将显示被触发的错误。虽然该错误被触发,但是应用程序不会被强制关闭,如图所示。

虽然使用uncaughtException事件可以捕捉任何未被处理的错误,但这是一种“粗鲁”的处理方法,有可能产生资源泄漏、内存泄漏等一系列非常恶劣的影响。例如,在本例中,客户端将永远得不到服务器端的响应。
为了解决这一问题,从Node.js v0.8版开始,提供domain模块。该模块中提供一个Domain对象,当应用程序在任何时刻抛出错误时可以通知该对象,然后由该对象来统一处理这些错误。
使用domain模块来处理未被处理的错误:
var http = require('http');
var domain = require('domain');
http.createServer(function (req, res) {
var d = domain.create();
d.once('error', function(err) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<head><meta charset="utf-8"/></head>');
res.write('服务器端接收客户端请求时发生以下错误:');
res.end(err.message);
});
d.run(function() {
if(req.url!=="/favicon.ico"){
noneexist();
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<head><meta charset="utf-8"/></head>');
res.end('你好\n');
}
});
}).listen(1337, "127.0.0.1");命令行窗口中运行该文件,接着在浏览器的地址栏中输入http://localhost:1337,虽然服务器端触发一个错误,但是客户端可以接收到错误信息,如图所示。

2、创建并使用Domain对象
首先需要使用domain模块中的create方法创建一个Domain对象,该方法的使用方式如下:
let domain = domain.create();
create方法没有任何参数,该方法返回被创建的Domain对象。该对象是一个继承了EventEmitter类的实例对象,当该对象捕获到任何错误时,触发该对象的error事件。可以通过监听该对象的error事件并指定事件回调函数的方法来实现当捕捉到错误时的处理。该回调函数的指定方法如下所示(代码中的domain代表一个Domain对象)。
domain.on('error', function(err) {
//事件回调函数代码略
});
在Domain对象的error事件回调函数中,使用一个参数,其参数值为被触发的错误对象。
在domain模块中,为Domain对象定义一个name属性值,可以使用该属性值来设置或获取该Domain对象的名称。
在Domain对象被创建后,需要指定该对象所监听的代码,我们需要将这些代码书写在一个函数中,并且使用Domain对象的run方法指定Domain对象监听该函数中的代码。当这些代码触发任何错误时,将被Domain对象捕获。Domain对象的run方法的指定方法如下所示(代码中的domain代表一个Domain对象)。
domain.run(fn)
在Domain对象的run方法中,使用一个参数,参数值为一个函数。当该函数中触发任何错误时,将被Domain对象捕获。
let domain = require('domain');
let rfs = require('fs');
let d = domain.create();
d.name='d1';
d.on('error', (err)=>{
console.error('%s捕获到错误!',d.name,err);
});
d.run(()=>{
process.nextTick(()=>{
setTimeout(()=>{ //模拟一个回调函数
rfs.open('non-existent file', 'r', (err, fd)=>{
if (err)
throw err;
});
}, 1000);
});
});d1捕获到错误! [Error: ENOENT: no such file or directory, open 'non-existent file'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: 'non-existent file',
domainThrown: true
}
3、隐式绑定与显示绑定
3.1、隐式绑定
当使用Domain对象的run方法指定Domain对象所要监听的函数时,函数中使用的所有继承了EventEmitter类的实例对象(包括各种用于读取或写入流数据的对象,以及客户端请求对象、服务器端响应对象等各种有可能触发事件的对象)都隐式地绑定到Domain对象上,如果这些对象触发错误,这些错误都将被Domain对象捕获。
有时,我们可能不想让代码中的某些对象所触发的错误被Domain对象捕获,或者想让这些错误被其他的Domain对象捕获。在这些场合下,应该将这些对象显式地绑定到Domain对象上。例如,我们有时可能想为HTTP服务器绑定一个Domain对象,而为每个客户端请求单独绑定一个Domain对象。
3.2、显示绑定
在实现显式绑定时,需要使用Domain对象的add方法,该方法的使用方式如下所示(代码中的domain代表一个Domain对象)。
domain.add(emitter)
在Domain对象的add方法中,使用一个参数,其参数值可以为一个需要被绑定的继承了EventEmitter类的实例对象。如果该实例对象触发了一个错误,该错误将被Domain对象所捕获并触发Domain对象的error事件。add方法的参数值也可以为一个通过setInterval方法或setTimeout方法返回的定时器。如果该定时器所执行的回调函数中抛出一个错误,那么该错误也将被Domain对象所捕获并触发Domain对象的error事件。
显示绑定domain示例:
在该示例代码中,我们创建一个HTTP服务器,并指定当服务器接收到客户端请求时,首先创建一个Domain对象,然后使用该对象的add方法分别将用于读取客户端请求数据的http.IncomingMessage对象与用于发送服务器端响应数据的http.ServerResponse对象绑定到Domain对象上,并指定当Domain对象捕获到错误时向客户端返回“服务器端接收客户端请求时发生以下错误:”字符串与被触发的错误消息。在http.IncomingMessage对象接收到客户端发送数据而触发的data事件回调函数中,首先使用一个不存在的方法noneexists,然后向客户端返回“你好。”字符串。
const http = require('http');
const domain = require('domain');
http.createServer((req, res)=>{
let reqd = domain.create();
//绑定对象
reqd.add(req);
reqd.add(res);
reqd.on('error', (err)=>{
res.writeHead(200);
res.write('服务器端接收客户端请求时发生以下错误:');
res.end(err.message);
});
res.writeHead(200);
req.on('data',()=>{
noneexists();
res.write('你好。');
res.end();
});
}).listen(1337);
接下来创建一个向HTTP服务器提交请求并发送数据的模块文件,代码如代码清单10-6所示。在该示例代码中,我们向在代码清单10-5所示示例中创建的HTTP服务器提交请求,指定当客户端接收到服务器端返回数据时在控制台中输出这些数据,并向HTTP服务器发送“你好。”字符串与“再见。”字符串。
const http = require('http');
let options = {
hostname: 'localhost',
port: 1337,
path: '/',
method: 'POST'
};
let req = http.request(options,(res)=>{
res.setEncoding('utf8');
res.on('data', (chunk)=>{
console.log('响应内容: '+chunk);
});
});
req.write('你好。');
req.end('再见。');
一个命令行窗口中运行server.js文件,在另一个命令行窗口中运行client.js文件,该命令行窗口中显示的结果如下:

3.3、取消显示绑定
在使用了Domain对象的add方法继承了EventEmitter类的实例对象与该Domain对象进行绑定后,可以使用Domain对象的remove方法取消这些对象的绑定,该方法的使用方式如下所示。
domain.remove(emitter)
在Domain对象的remove方法中,使用一个参数,其参数值为一个需要被解除绑定的继承了EventEmitter类的实例对象。在解除绑定后,如果该实例对象触发了一个错误,该错误将不再被Domain对象所捕获。
修改createServer方法所使用的callback参数值回调函数代码,在使用了Domain对象的add方法之后,使用Domain对象的remove方法解除http.IncomingMessage对象与Domain对象的绑定,修改后的代码如下所示。
const http = require('http');
const domain = require('domain');
http.createServer((req, res)=>{
let reqd = domain.create();
//绑定对象
reqd.add(req);
reqd.add(res);
reqd.on('error', (err)=>{
res.writeHead(200);
res.write('服务器端接收客户端请求时发生以下错误:');
res.end(err.message);
});
res.writeHead(200);
//取消绑定
reqd.remove(req);
req.on('data',()=>{
noneexists();
res.write('你好。');
res.end();
});
}).listen(1337);
然后在一个命令行窗口中运行server.js文件,在另一个命令行窗口中运行client.js文件,在用于运行server.js文件的窗口中将显示由于运行一个不存在的方法而导致一个错误被触发,如图所示。

4、绑定回调函数与拦截回调函数
4.1、绑定回调函数
可以使用Domain对象的bind方法将一个回调函数与该Domain对象进行绑定,该方法的使用方式如下所示。
domain.bind(callback)
在Domain对象的bind方法中,使用一个参数,其参数值为一个回调函数,用于指定需要被绑定的回调函数。bind方法返回一个函数,该函数将代替原回调函数被调用。当该函数被调用时,将内部调用bind方法的参数值回调函数,当该回调函数中抛出任何错误时,Domain对象将捕获这些错误,在Domain对象的error事件回调函数中的参数值为该错误对象。
查看一个bind方法的使用示例,代码如代码清单10-7所示。在该示例代码中,我们首先创建一个Domain对象,然后使用fs模块中的readFile方法读取一个并不存在的文件,并使用Domain对象的bind方法将读取文件操作结束时执行的回调函数与Domain对象绑定到一起。在回调函数中,我们指定,当读取文件操作发生错误时,将该错误抛出。在Domain对象的error事件回调函数中,我们指定,当捕获到错误时,在控制台中输出该错误。
const fs=require('fs');
const domain = require('domain');
let d = domain.create();
fs.readFile('./test.txt', d.bind(function(err, data) {
if(err)
throw err;
else
console.log(data);
}));
d.on('error', (err)=>{
console.log('读取文件时发生以下错误:');
console.log(err);
});

4.2、拦截回调函数
可以使用Domain对象的intercept方法拦截一个回调函数中触发的错误。该方法的作用及其使用方法与Domain对象的bind方法有些相似,都是捕获一个回调函数中所触发的错误,它们的区别在于,在使用bind方法时,回调函数中必须使用throw关键字抛出该错误,而在使用intercept方法时,回调函数不需要再抛出该错误,该错误将直接被Domain对象拦截。
intercept方法的使用方式如下所示。
domain.intercept(callback)
在Domain对象的intercept方法中,使用一个参数,其参数值为一个回调函数,用于指定需要被拦截的回调函数。intercept方法返回一个函数,该函数将代替原回调函数被调用。当该函数被调用时,将内部调用bind方法的参数值回调函数,当该回调函数中抛出任何错误时,将被Domain对象所捕获,在Domain对象的error事件回调函数中的参数值为该错误对象。
const fs=require('fs');
const domain = require('domain');
let d = domain.create();
fs.readFile('./test.txt', d.intercept(function(err, data) {
console.log(data);
}));
d.on('error', (err)=>{
console.log('读取文件时发生以下错误:');
console.log(err);
});

5、domain堆栈的弹出与推入
在Node.js中,当使用Domain对象的run方法、bind方法与intercept方法指定Domain对象所需监听的函数时,将把该Domain对象推入到domain模块所维护的一个domain堆栈中。可以通过domain中的_stack属性值来查看domain堆栈中的内容。
5.1、推入
在该示例代码中,分别创建两个Domain对象并分别将其命名为d1与d2,然后输出domain堆栈中的原始内容。接着,使用d1对象的run方法指定其所监听的函数,并在该函数代码中指定在控制台中输出运行了d1对象的run方法后堆栈中的内容,然后使用d2对象的run方法指定其所监听的函数,并在该函数代码中指定在控制台中输出运行了d2对象的run方法后堆栈中的内容。
const domain = require('domain');
let d1 = domain.create();
d1.name="d1";
let d2 = domain.create();
d2.name = "d2";
console.log('原始堆栈:');
console.log(domain._stack);
d1.run(()=>{
console.log('d1对象:');
console.log(d1);
console.log('运行d1对象后的堆栈内容:');
console.log(domain._stack);
});
d2.run(()=>{
console.log('d2对象:');
console.log(d2);
console.log('运行d2对象后的堆栈内容:');
console.log(domain._stack);
});
运行d1对象后的堆栈内容:
[
Domain {
_events: [Object: null prototype] {
removeListener: [Function: updateExceptionCapture],
newListener: [Function: updateExceptionCapture]
},
_eventsCount: 2,
_maxListeners: undefined,
members: [],
name: 'd1',
[Symbol(kCapture)]: false,
[Symbol(kWeak)]: WeakReference {}
}
]
d2对象:
Domain {
_events: [Object: null prototype] {
removeListener: [Function: updateExceptionCapture],
newListener: [Function: updateExceptionCapture]
},
_eventsCount: 2,
_maxListeners: undefined,
members: [],
name: 'd2',
[Symbol(kCapture)]: false,
[Symbol(kWeak)]: WeakReference {}
}
运行d2对象后的堆栈内容:
[
Domain {
_events: [Object: null prototype] {
removeListener: [Function: updateExceptionCapture],
newListener: [Function: updateExceptionCapture]
},
_eventsCount: 2,
_maxListeners: undefined,
members: [],
name: 'd2',
[Symbol(kCapture)]: false,
[Symbol(kWeak)]: WeakReference {}
}
]
从这个结果中可以看出,在未运行任何Domain对象的run方法、bind方法与intercept方法时,domain堆栈中的内容为空,而在运行一个Domain对象的run方法、bind方法与intercept方法之后,该Domain对象被推入domain堆栈,另外,在运行另一个与堆栈中当前Domain对象并级的Domain对象的run方法、bind方法与intercept方法之后,另外,堆栈中当前Domain对象被弹出堆栈,后一个Domain对象被推入domain堆栈。
5.2、弹出
可以使用Domain对象的exit方法将该Domain对象从domain堆栈中弹出。该Domain对象从domain堆栈中弹出后将不能再捕获任何错误。
首先创建了一个Domain对象,并指定该Domain对象的error事件函数,在该函数代码中指定当Domain对象捕获到错误时在控制台中输出“Domain对象捕获到错误。”字符串。然后,输出domain堆栈中的原始内容。接下来,使用Domain对象的run方法指定其所监听的函数,在该函数代码中指定在控制台中输出运行了Domain对象的run方法后堆栈中的内容,最后抛出一个自定义错误。
const domain = require('domain');
let d = domain.create();
d.on('error',(err)=>{
console.log('Domain对象捕获到错误。');
});
console.log('原始堆栈:');
console.log(domain._stack);
d.run(()=>{
console.log('运行domain对象后的堆栈内容:');
console.log(domain._stack);
throw new Error("error");
});
运行domain对象后的堆栈内容:
[
Domain {
_events: [Object: null prototype] {
removeListener: [Function: updateExceptionCapture],
newListener: [Function: updateExceptionCapture],
error: [Function (anonymous)]
},
_eventsCount: 3,
_maxListeners: undefined,
members: [],
[Symbol(kCapture)]: false,
[Symbol(kWeak)]: WeakReference {}
}
]
Domain对象捕获到错误。
在Domain对象的run方法所指定监听的函数中,添加使用Domain对象的exit方法,并且暂时注释掉函数中用于抛出自定义错误的代码,修改后的代码如下所示。
const domain = require('domain');
let d = domain.create();
d.on('error',(err)=>{
console.log('Domain对象捕获到错误。');
});
console.log('原始堆栈:');
console.log(domain._stack);
d.run(()=>{
//出栈
d.exit();
console.log('运行domain对象后的堆栈内容:');
console.log(domain._stack);
throw new Error("error");
});
原始堆栈:
[]
运行domain对象后的堆栈内容:
[]
/Users/acton_zhang/前端/vscodeworkspace/node/demo/index.js:13
throw new Error("error");
^
Error: error
at Domain.<anonymous> (/Users/acton_zhang/前端/vscodeworkspace/node/demo/index.js:13:11)
at Domain.run (domain.js:373:14)
at Object.<anonymous> (/Users/acton_zhang/前端/vscodeworkspace/node/demo/index.js:8:3)
at Module._compile (internal/modules/cjs/loader.js:1118:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1138:10)
at Module.load (internal/modules/cjs/loader.js:982:32)
at Function.Module._load (internal/modules/cjs/loader.js:875:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
at internal/main/run_main_module.js:17:47在使用了Domain对象的exit方法后,该对象将不能再捕获任何错误。
可以使用Domain对象的enter方法将一个Domain对象推入domain堆栈中并使该Domain对象变为当前使用的Domain对象。
在Domain对象的run方法所指定监听的函数中,在使用了Domain对象的exit方法之后,使用该对象的enter方法再将它推入domain堆栈,并且在控制台中显示使用Domain对象的enter方法后domain堆栈中的内容。修改后的代码如下所示。
const domain = require('domain');
let d = domain.create();
d.on('error',(err)=>{
console.log('Domain对象捕获到错误。');
});
console.log('原始堆栈:');
console.log(domain._stack);
d.run(()=>{
//出栈
d.exit();
console.log('运行exit方法后的堆栈内容:');
console.log(domain._stack);
//入栈
d.enter();
console.log('运行enter方法后的堆栈内容:');
console.log(domain._stack);
throw new Error("error");
});
运行enter方法后的堆栈内容:
[
Domain {
_events: [Object: null prototype] {
removeListener: [Function: updateExceptionCapture],
newListener: [Function: updateExceptionCapture],
error: [Function (anonymous)]
},
_eventsCount: 3,
_maxListeners: undefined,
members: [],
[Symbol(kCapture)]: false,
[Symbol(kWeak)]: WeakReference {}
}
]
Domain对象捕获到错误。
如果在一个Domain对象所监听的函数中使用另一个Domain对象的enter方法,另一个Domain对象将被推入到domain堆栈中,然后改为使用该Domain对象来捕捉第一个Domain对象所监听的函数中抛出的错误。
接下来看一个在Domain对象中嵌套使用另一个Domain对象的示例。在该示例代码中,创建两个Domain对象,并分别将其命名为d1与d2。指定d1对象的error事件函数,在该函数代码中指定当d1对象捕获到错误时在控制台中输出“d1对象捕获到错误。”字符串,指定d2对象的error事件函数,在该函数代码中指定当d2对象捕获到错误时在控制台中输出“d2对象捕获到错误。”字符串。然后,输出domain堆栈中的原始内容。接下来,使用d1对象的run方法指定其所监听的函数,在该函数代码中使用d2对象的run方法指定其所监听的函数,在这个嵌套的函数代码中指定在控制台中输出在运行了d2对象的run方法之后堆栈中的内容,并且抛出一个自定义错误。
const domain = require('domain');
let d1 = domain.create();
d1.name="d1";
d1.on('error',(err)=>{
console.log('d1对象捕获到错误。');
});
let d2 = domain.create();
d2.name = "d2";
d2.on('error',(err)=>{
console.log('d2对象捕获到错误。');
});
console.log('原始堆栈:');
console.log(domain._stack);
d1.run(()=>{
d2.run(()=>{
console.log('最终堆栈:');
console.log(domain._stack);
throw new Error("first");
});
});
原始堆栈:
[]
最终堆栈:
[
Domain {
_events: [Object: null prototype] {
removeListener: [Function: updateExceptionCapture],
newListener: [Function: updateExceptionCapture],
error: [Function (anonymous)]
},
_eventsCount: 3,
_maxListeners: undefined,
members: [],
name: 'd1',
[Symbol(kCapture)]: false,
[Symbol(kWeak)]: WeakReference {}
},
Domain {
_events: [Object: null prototype] {
removeListener: [Function: updateExceptionCapture],
newListener: [Function: updateExceptionCapture],
error: [Function (anonymous)]
},
_eventsCount: 3,
_maxListeners: undefined,
members: [],
name: 'd2',
[Symbol(kCapture)]: false,
[Symbol(kWeak)]: WeakReference {}
}
]
d2对象捕获到错误。
如果在d2对象的run方法中使用d1对象的enter方法,将把d1对象推入domain堆栈的最顶层,在使用了d1对象的enter方法之后的代码中抛出的错误将被d1对象捕获,如图所示。
d1.run(()=>{
d2.run(()=>{
//d1进入栈顶
d1.enter();
console.log('最终堆栈:');
console.log(domain._stack);
throw new Error("first");
});
});
原始堆栈:
[]
最终堆栈:
[
Domain {
_events: [Object: null prototype] {
removeListener: [Function: updateExceptionCapture],
newListener: [Function: updateExceptionCapture],
error: [Function (anonymous)]
},
_eventsCount: 3,
_maxListeners: undefined,
members: [],
name: 'd1',
[Symbol(kCapture)]: false,
[Symbol(kWeak)]: WeakReference {}
},
Domain {
_events: [Object: null prototype] {
removeListener: [Function: updateExceptionCapture],
newListener: [Function: updateExceptionCapture],
error: [Function (anonymous)]
},
_eventsCount: 3,
_maxListeners: undefined,
members: [],
name: 'd2',
[Symbol(kCapture)]: false,
[Symbol(kWeak)]: WeakReference {}
},
Domain {
_events: [Object: null prototype] {
removeListener: [Function: updateExceptionCapture],
newListener: [Function: updateExceptionCapture],
error: [Function (anonymous)]
},
_eventsCount: 3,
_maxListeners: undefined,
members: [],
name: 'd1',
[Symbol(kCapture)]: false,
[Symbol(kWeak)]: WeakReference {}
}
]
d1对象捕获到错误。
如果在Domain对象中嵌套使用其他Domain对象,则在最外层的Domain对象的exit方法被调用后,该对象及其内部嵌套的所有Domain对象都被弹出domain堆栈,所有被弹出的Domain对象都不能再捕获任何错误。
d1.run(()=>{
d2.run(()=>{
//d1出栈
d1.exit();
console.log('最终堆栈:');
console.log(domain._stack);
// throw new error("first");
});
});
原始堆栈: [] 最终堆栈: []
在使用了d1对象的exit方法后domain堆栈中的Domain对象全部弹出。
6、domain对象的销毁
在一个Domain对象不再需要使用之后,可以使用该对象的dispose方法将其销毁。在该对象被销毁后,所有与该对象绑定的对象或回调函数都被解除绑定,这些对象或回调函数所触发的错误不再被该Domain对象捕获,在Domain对象的run方法中指定的函数将不再被运行,与Domain对象绑定的定时器也将不再被运行。
d.dispose();
到此这篇关于Node.js domain模块处理错误的问题小结的文章就介绍到这了,更多相关Node.js domain模块内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Node.js使用sharp库实现PNG图片转换为WebP格式
在 Node.js 中,可以使用 sharp 库将 PNG 图片转换为 WebP 格式,sharp 是一个高性能的图像处理库,支持多种图像格式的转换和处理,以下是如何使用 sharp 将 PNG 图片转换为 WebP 的详细步骤,需要的朋友可以参考下2024-12-12
npm install的--save和--save-dev使用说明(推荐)
这篇文章主要介绍了npm install的--save和--save-dev使用说明,文中给大家提到了各个命令的区别及各种安装参数的区别,需要的朋友可以参考下2022-08-08
nodejs中用npm初始化来创建package.json的实例讲解
今天小编就为大家分享一篇nodejs中用npm初始化来创建package.json的实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2018-10-10


最新评论