对于开发人员来说,代码错误是一个巨大的挑战,它往往会导致不必要的时间消耗。我们可能很快就会把矛头指向编程语言或环境,但我们也应该承认,这些错误中有很多是由开发人员的失误和我们使用这些工具的方式造成的。
Node.js 已经存在了相当长的一段时间,在构建强大而复杂的网络服务方面发挥了重要作用,这些服务能够有效扩展并经受住时间的考验。但是,与其他运行时环境或平台一样,Node.js 也容易受到开发人员产生的错误的影响。
在本文中,我们将介绍您可能会遇到的一些最常见的 Node.js 错误,并讨论如何解决这些错误。
主要内容如下:
Stream(流)是 Node.js 中的一个基本概念,用于读写异步数据源(如文件、套接字或 HTTP 请求)。在流的生命周期中,随时都可能发生错误。
流会在各种操作(如读取、写入、管道或转换数据)过程中产生错误。错误通过流的错误事件发出。如果不为流附加错误处理程序,错误就会在事件循环中传播,并可能导致应用程序崩溃。
下面是流中未处理异常的示例:
const fs = require('fs');const readStream = fs.createReadStream('nonexistent-file.txt');// Without an error handler, this error will crash the application.readStream.pipe(process.stdout);
如果在客户端的连接被突然终止时未附加错误处理程序,readStream 在发生错误时可能无法关闭。相反,它将无限期保持打开状态,在应用程序中引发内存泄漏。
这可能导致意外的行为,并可能导致出现像在下面示例中显示的 unhandled stream error in pipe 之类的错误:
stream.js:60 throw er; // Unhandled stream error in pipe. ^Error: socket hang up at createHangUpError (_http_client.js:200:15) at Socket.socketOnEnd (_http_client.js:285:23) at emitNone (events.js:72:20) at Socket.emit (events.js:166:7) at endReadableNT (_stream_readable.js:905:12) at nextTickCallbackWith2Args (node.js:437:9) at process._tickCallback (node.js:351:17)
要解决 Node.js 流中未处理的异常错误,可以使用多种解决方案之一。让我们一起来看看。
在日常开发中,我们要始终附加错误事件处理程序,以捕获和处理在流操作过程中出现的错误。这样可以确保错误被捕获并妥善管理,防止应用程序崩溃:
const fs = require('fs');const readStream = fs.createReadStream('example-file.txt');readStream.on('error', (err) => { console.error('An error occurred:', err.message);});readStream.pipe(process.stdout);
在处理与流交互的同步代码时,可以用 try-catch 将代码封装起来,以便有效处理错误。这将确保程序在发生错误时不会崩溃,并以可控的方式处理错误:
const fs = require('fs');try { const readStream = fs.createReadStream('example-file.txt', 'utf8'); const dataPromise = new Promise((resolve, reject) => { let data = ''; readStream.on('data', (chunk) => { data += chunk; }); // Handle errors from the stream readStream.on('error', (err) => { reject(err); // Reject the promise if an error occurs }); // When the stream ends, resolve the promise with the data readStream.on('end', () => { resolve(data); }); }); const fileData = await dataPromise; console.log('File contents:', fileData);} catch (err) { console.error('An error occurred:', err.message); // Log error to the console}
在上面的代码中,我们创建了一个 try-catch 块,该块封装了一个 Promise,当数据流成功结束时,该 Promise 将被 resolve;如果发生错误,该承诺将被 reject。然后在 catch 代码块中捕获错误并记录。
前面的选项可以有效地处理错误,但是当使用 pipeline 方法时,附加事件处理程序到每个流上可能会变得难以管理。相反,管道方法提供了一种更清晰、更易于管理的处理错误方式。
管道方法是一个流方法,它接受三个参数:可读流(流的源头)、可写流(流的目标)和在过程中发生错误时将被调用的回调函数:
const fs = require('fs');const { pipeline } = require('stream');const readStream = fs.createReadStream("inputexample.txt");const writeStream = fs.createWriteStream("outputexample.txt");pipeline( readStream, // Readable stream writeStream, // Writable stream (err) => { // Callback function to handle errors if (err) { console.error("Pipeline failed:", err); } else { console.log("Pipeline succeeded"); } });
pipeline 方法在文件输入/输出和网络操作中特别有用,它提供了一种更清晰、更强大的方式,可以将数据从可读流传输到可写流,同时有效地处理错误。
finished() 函数是一个处理流清理和完成逻辑的流方法。当流不再可读或可写,或者因为过早终止(如取消的HTTP请求)而出现错误时,它会被触发。
当流成功结束时,该函数发出 end 或 finish 事件。但是,该函数会忽略这些事件,并代替调用回调函数,将其作为第二个参数以处理意外错误并防止应用程序崩溃:
const { finished } = require('node:stream');const fs = require('node:fs');const readStream = fs.createReadStream('input.txt');finished(readStream, (err) => { if (err) { console.error('Read stream encountered an error:', err); } else { console.log('Read stream has finished successfully.'); }});
JavaScript 堆内存不足错误可能由多种因素引起,但最常见的是应用程序中的内存泄漏。当应用程序分配内存不再需要时没有及时释放,就会发生内存泄漏。
这可能发生在应用程序创建的对象从不被删除,或者应用程序保留对不再使用的对象的引用时。随着时间的推移,内存泄漏可能导致应用程序耗尽内存并崩溃:
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
此错误相当模糊,新手开发人员通常不知道如何解决这样的错误。让我们来看一下 Node.js 中内存泄漏的最常见原因。
当与数据库或其他资源的连接未正确关闭时,连接将保持打开状态并占用内存:
// 模拟一个长时间运行的操作,而不关闭连接app.get('/unclosed', (req, res) => { // 在真实的情况下,这可能是一个数据库查询、文件读取等。 setTimeout(() => { // 这个响应不会被发送,连接保持打开 console.log('请求已处理,但响应未被发送。'); }, 5000);});const port = 3000;app.listen(port, () => { console.log(`服务器在端口上监听${port}。`);});
在这个示例中,服务器不会向客户端发送响应。这会导致连接保持打开,而随着时间的推移,这些开放的连接可能会占用内存并导致性能问题。
为了避免这种情况,您的服务器应该向客户端发送响应,即使只是一个简单的响应表示已收到请求。服务器还应该在发送响应后关闭连接。
当对象不再需要时,应该对其进行处理以释放它占用的内存。未处理对象可能导致内存泄漏:
const mysql = require('mysql2');const pool = mysql.createPool({ host: 'localhost', user: 'username', password: 'password', database: 'mydb', connectionLimit: 10, // 限制并发连接数});// 模拟不释放连接的查询function querySim() { pool.query('SELECT 1 + 1 AS result', (error, results) => { if (error) { console.error('错误:',error); } else { console.log('结果:',results[0].result); } });}// 定期模拟查询(例如每1秒)setInterval(querySim, 1000);
在这个示例中,querySim 函数将连续不断地使用连接池创建新的数据库连接,而没有释放它们,从而导致内存泄漏。
为了避免内存泄漏,始终在使用完资源后释放资源。对于数据库连接,请使用 pool.end() 来有效地关闭连接池中的连接。
当两个对象相互引用时,它们可以创建一个循环引用。这可能会阻止任何一个对象被垃圾收集,从而导致内存泄漏:
// 为模拟在生产环境中的内存泄漏重复该过程setInterval(() => { // 创建具有循环引用的新对象 const newObj1 = {}; const newObj2 = {}; newObj1.child = newObj2; newObj2.parent = newObj1;}, 1000);
上面的示例模拟了一个情况,其中会反复创建两个对象obj1和obj2。每个对象都有一个属性,指向另一个对象,从而创建了一个循环引用,这会阻止垃圾收集。
如果这个过程持续一段时间,它可能会导致内存泄漏,因为具有循环引用的对象不会被垃圾收集,并且会继续占用内存。这最终可能导致JavaScript堆内存不足的错误。
为了防止内存泄漏,当不再需要对象时,要打破循环引用。将属性设置为null,或者在不再使用对象时使用其他打破循环引用的技术。
以下是如何使用 null 来中断先前示例中引用的方法:
let newObj1,newObj2;function circularObjects() { // 如果先前创建了对象,请打破它们的引用 if (newObj1) { newObj1.child = null; } if (newObj2) { newObj2.parent = null; } // 创建具有循环引用的新对象 newObj1 = {}; newObj2 = {}; newObj1.child = newObj2; newObj2.parent = newObj1;}setInterval(circularObjects, 1000);
JavaScript 堆内存不足错误也可能在应用程序增大并使用更多占用 Node.js 分配的可用堆内存的对象时发生(默认情况下为1.5GB)。
要解决此错误,您可以使用以下命令增加最大堆内存:
Linux或macOS:
node --max-old-space-size=4096 server.js
Windows:
运行以下命令:
node --max-old-space-size=4096 server.js
此命令将以 4GB 的内存限制启动您的 Node.js 应用程序。
在 Node.js 中避免内存泄漏的可靠方法是遵循最佳编码实践,并使用 Node.js Inspector 等工具有效地监视和管理内存使用。
环境兼容性错误可能会在将针对特定环境(如Web浏览器)编写的代码移植到其它不可用或行为不同的环境时出现。让我们看一下一些常见的引用错误。
ReferenceError: document is not defined 错误是开发人员经常遇到的最常见的环境兼容性错误,这些开发人员习惯于在Web浏览器环境中工作,但对 Node.js 还不熟悉:
ReferenceError: document is not definedat Object.<anonymous> (C:\Users\Desktop\main.js:9:18)at Module._compile (module.js:460:26)at Object.Module._extensions..js (module.js:478:10)at Module.load (module.js:355:32)at Function.Module._load (module.js:310:12)at Function.Module.runMain (module.js:501:10)at startup (node.js:129:16)at node.js:814:3
该错误表示您正在尝试访问document全局对象,该对象通常在 Web 浏览器的 DOM 中可用。然而,Node.js 是一个运行时环境,默认情况下没有 DOM 。这就是为什么在 Node.js 环境中尝试访问 document 会导致 ReferenceError: document is not defined的原因:
// This code tries to access the `document` object, which is not available outside a web browserconst title = document.title;console.log(`Document title: ${title}`); // ReferenceError: document is not defined
如果您的任务需要 DOM,您可以使用提供了一个 DOM 的库,如 Cheerio 或 Puppeteer。否则,您可以通过使用 typeof 运算符检查环境来确定代码所在的特定环境,然后再访问 document 对象:
if (typeof document === "object") { // 在浏览器中运行特定于浏览器的代码 document.querySelectorAll("p"); document.getElementById("table"); console.log("在浏览器环境中运行");} else { // 在非浏览器代码中运行 console.log("在非浏览器环境中运行");}
这段代码将检查 document 对象在运行时环境中是否可用。如果它返回true,则执行if块中的代码;否则,执行else块中的代码。
ReferenceError: window is not defined 错误发生在您尝试访问 window 对象时,其中 window 对象是特定于Web浏览器的,并且在 Node.js 运行时环境中不可用。window 对象是 Web 浏览器中的全局对象。它包含用于与浏览器和其窗口进行交互的属性和方法。
在Node.js运行时环境中,全局对象称为global,并且不包含window对象。因此,如果您尝试在Node.js运行时环境中访问window对象,您将收到 ReferenceError: window is not defined
与前面的错误类似,您可以通过使用条件语句和typeof运算符在执行之前检查代码是否在适当的环境中来修复ReferenceError: window未定义错误:
function nodeTask() { console.log('执行特定于Node的任务');}function browserTask() { console.log('执行特定于浏览器的任务');}// 环境特定的代码执行if (typeof window === 'undefined') { // 检查window是否未定义,通常表示Node.js环境 nodeTask(); // 执行Node特定代码} else { // 如果window被定义,假设它是浏览器环境 browserTask(); // 执行浏览器特定的代码}
ReferenceError: XMLHttp Request is not defined 错误是在Node.js环境中尝试使用XMLHttpRequest构造函数发起HTTP请求时发生的,如下所示:
try { const xhr = new XMLHttpRequest(); xhr.open('GET', 'https://example.com', true); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText); } }; xhr.send();} catch (error) { console.error('Error:', error);}
XMLHttpRequest 方法是一个特定于浏览器的 API。然而,与 window 和 document 不同,它不是全局对象,而是一个与服务器进行交互的构造函数。由于它的操作本质上是不可知的,所以很容易在非浏览器环境中(如Node.js)中使用XMLHttpRequest方法时犯下错误。
要解决 ReferenceError: XMLHttp Request is not defined 错误,请使用替代包,axios、got或者直接使用高版本 Node.js 内置的fetch,它们是最新的,并提供更开发者友好的与服务器交互的方式。您可以使用以下命令安装这些包:
npm install axiosnpm install got
网络和通信错误是在您的 Node.js 应用程序和其他系统(如数据库、Web服务器和其他网络资源)之间进行网络通信时通常发生的一系列错误。这些错误可能由与网络连接性、数据传输等相关的各种因素引起。让我们来看看Node.js中一些常见的网络和通信错误以及如何解决它们。
Error: read ECONNRESET 错误发生在与远程服务器的连接意外关闭,通常在收到响应之前导致 HTTP 请求失败时:
Error: read ECONNRESETat errnoException (server.js:900:11)at TCP.onread (server.js:555:19)
这可能由许多因素引起,例如远程服务器过载、Node.js 应用程序发送过多数据或远程服务器停电。
要解决此错误,请执行以下操作之一:
Error: connect ECONNREFUSED 错误发生在无法建立从 Node.js 应用程序到远程服务器的连接时:
Error: connect ECONNREFUSED at errnoException (net.js:770:11) at Object.afterConnect [as oncomplete] (net.js:761:19)
此错误可能由多种因素引起,例如远程服务器宕机、Node.js 应用程序无法到达远程服务器、远程服务器拒绝连接或将请求发送到错误的终端节点或端口。
要解决此错误,请检查远程服务器的状态,验证您的 Node.js 应用程序是否正在运行并能够访问远程服务器,并确保远程服务器不会拒绝连接。
Error: listen EADDRINUSE: address already in use 不是一种通信错误,而是在您尝试启动 Node.js 应用程序的端口已被另一个进程使用时发生的错误:
Error: listen EADDRINUSE: address already in use :::3000Emitted 'error' event on Server instance at: at emitErrorNT (node:net:1544:8) at process.processTicksAndRejections (node:internal/process/task_queues:84:21) { code: 'EADDRINUSE', errno: -98, syscall: 'listen', address: '::', port: 3000}
在上面的示例中,错误消息表示端口 3000 已被另一个进程使用。这意味着该端口当前被占用,无法用于当前请求。
要解决此错误,您可以停止使用指定端口的进程,或者将您的应用程序配置为在不同的端口上运行。要执行前者,在终端中执行以下命令:
npx kill-port 3000
注意,此命令将立即终止进程,而不提供保存未保存数据的任何机会。
对于 macOS,请使用以下命令查看正在使用该端口的进程的信息:
lsof -i: 3000
您将收到一个包含进程ID(PID)的响应。复制该数字,并使用以下命令运行:
kill -9 <PID number>
此命令将终止进程,之后您可以无错误地启动 Node.js 应用程序。
错误:write EPIPE 错误:write EPIPE或“broken pipe”错误发生在您的Node.js应用程序尝试向已关闭或意外终止的连接的另一端写入数据时。当应用程序不断尝试写入已关闭的连接时,通常会发生此错误:
Error: write EPIPE at errnoException (net.js:770:11) at Socket._write (net.js:552:19) at Socket.write (net.js:511:15)
要修复 Error: write EPIPE 错误,检查您要写入的流或套接字是否仍处于活动状态并正在运行,然后再尝试写入数据。使用try-catch 块捕捉错误,然后采取适当的操作,例如关闭流或记录错误。
以下是另外一些您可能在将来遇到的常见 Node.js错误。
当您在不是数组的数据类型上调用 find() 方法时,会出现 .find 不是函数错误:
const data = { x: 5, y: 10, z: 15,};data.find(el => el > 1 );
在此示例中,find() 方法在对象上被调用。由于 find() 方法仅适用于数组,将抛出以下错误:
TypeError: arr.find is not a function
Uncaught SyntaxError: Unexpected identifier 错误表明您的代码中存在语法错误或拼写错误的关键字,这些错误阻止解释器正确解析您的代码。当声明变量、类或方法时,错误特别常见,错误地使用大写字母而不是小写字母作为关键字时:
Let name = "same"; // L should be lowercase lConst age = 20; // C should be lowercase c
要解决未捕获的语法错误:意外标识符错误,请导航到发生错误的行,并确保关键字的拼写正确。还要寻找丢失的符号,如逗号、冒号、括号或圆括号。通过识别错误的源头,您应该能够轻松解决它。建议引入 ESLint 来规范代码,提升代码质量。
代码错误是使我们成为优秀开发者的重要部分。通过理解错误以及为什么发生错误,我们可以更深入地了解我们正在工作的编程语言或环境。
在本文中,我们回顾了一些常见的 Node.js 错误并讨论了如何修复它们。希望本文能帮助您更好地了解 Node.js 和 JavaScript。祝大家快乐编程每一天!