目錄
錯誤1:阻塞事件循環
錯誤2:多次呼叫一個回呼函數
錯誤3:深層嵌套的回調函數
错误4:期待回调函数同步执行
错误5:给“exports” 赋值,而不是“module.exports”
错误6:从回调里抛出错误
错误7:认为 Number 是一种整型数据格式
错误8:忽略了流式 API 的优势
错误9:出于 Debug 的目的使用 Console.log
错误10:不使用监控程序
首頁 web前端 前端問答 nodejs有哪十大常見錯誤

nodejs有哪十大常見錯誤

Jan 13, 2022 pm 05:14 PM
node.js

node的十大常見錯誤:1、阻塞事件循環;2、多次呼叫一個回呼函數;3、深層巢狀的回呼函數;4、期待回呼函數同步執行;5、給「exports 「賦值;6、從回呼中拋出錯誤;7、認為Number是一種整數資料格式;8、忽略流式API的優勢等等。

nodejs有哪十大常見錯誤

本教學操作環境:windows7系統、nodejs 12.19.0版,DELL G3電腦。

自 Node.js 問世以來,它獲得了大量的讚美和批判。這種爭論會一直持續,短時間內都不會結束。而在這些爭論中,我們常常忽略掉所有語言和平台都是基於一些核心問題來批判的,就是我們怎麼去使用這些平台。無論使用 Node.js 編寫可靠的程式碼有多難,而編寫高並發程式碼又是多麼的簡單,這個平台終究是有那麼一段時間了,而且被用來創建了大量的健壯而又復雜的 web 服務。這些 web 服務不僅擁有良好的擴展性,而且透過在網路上持續的時間證明了它們的健壯性。

然而就像其它平台一樣,Node.js 很容易令開發者犯錯。這些錯誤有些會降低程式效能,有些則會導致 Node.js 不可用。在本文中,我們會看到 Node.js 新手常犯的 <span class="pun">十種錯誤</span>#,以及如何去避免它們。

錯誤1:阻塞事件循環

Node.js(如瀏覽器)裡的 JavaScript 提供了一個單執行緒環境。這意味著你的程式不會有兩塊東西同時在運行,取而代之的是非同步處理 I/O 密集操作所帶來的並發。比如說Node.js 給資料庫發起一個請求去獲取一些資料時,Node.js 可以集中精力在程式的其他地方:

// Trying to fetch an user object from the database. Node.js is free to run other parts of the code from the moment this function is invoked..	
db.User.get(userId, function(err, user) {	
    // .. until the moment the user object has been retrieved here	
})
登入後複製

然而,在一個有數千個客戶端連接的Node.js實例裡,一小段CPU 計算密集的程式碼會阻塞住事件循環,導致所有客戶端都要等待。 CPU 運算密集程式碼包含了嘗試排序一個龐大的陣列、跑一個耗時很長的函數等等。例如:

function sortUsersByAge(users) {	
    users.sort(function(a, b) {	
        return a.age &amp;lt; b.age ? -1 : 1	
    })	
}
登入後複製

在一個小的「users」 陣列上呼叫「sortUsersByAge」 方法是沒有任何問題的,但如果是在一個大陣列上,它會對整體效能造成巨大的影響。如果這種事情必須做,而且你能確保事件循環上沒有其他事件在等待(例如這只是一個 Node.js 命令列工具,而且它不在乎所有事情都是同步工作的)的話,那這沒有問題。但是,在一個 Node.js 伺服器試圖給上千用戶同時提供服務的情況下,它就會引發問題。

如果這個 users 陣列是從資料庫取得的,那麼理想的解決方案就是從資料庫中拿出已排好序的資料。如果事件循環被一個計算金融交易資料歷史總和的循環所阻塞,這個計算循環應該被推到事件循環外的佇列中執行以免佔用事件循環。

如你所見,解決這類錯誤沒有銀彈,只有針對每種情況單獨解決。基本概念是不要在處理客戶端並發連線的 Node.js 執行個體上做 CPU 運算密集型工作。

錯誤2:多次呼叫一個回呼函數

一直以來 JavaScript 都依賴回呼函數。在瀏覽器裡,事件都是透過傳遞事件物件的引用給一個回呼函數(通常都是匿名函數)來處理。在 Node.js 裡,回呼函數曾經是與其他程式碼非同步通訊的唯一方式,直到 promise 出現。回調函數現在仍在使用,而且許多開發者仍然圍繞著它來設定他們的 API。一個跟使用回調函數相關的常見錯誤是多次呼叫它們。通常,一個封裝了一些非同步處理的方法,它的最後一個參數會被設計為傳遞一個函數,這個函數會在非同步處理完後被呼叫:

module.exports.verifyPassword = function(user, password, done) {	
    if(typeof password !== ‘string’) {	
        done(new Error(‘password should be a string’))	
        return	
    }	
    computeHash(password, user.passwordHashOpts, function(err, hash) {	
        if(err) {	
            done(err)	
            return	
        }	
        done(null, hash === user.passwordHash)	
    })	
}
登入後複製

注意到除了最後一次,每次“done” 方法被呼叫之後都會有一個return 語句。這是因為呼叫回調函數不會自動結束目前方法的執行。如果我們註解掉第一個 return 語句,然後傳一個非字串型別的 password 給這個函數,我們依然會以呼叫 computeHash 方法結束。根據 computeHash 在這種情況下的處理方式,「done」 函數會被呼叫多次。當傳過去的回呼函數被多次呼叫時,任何人都會被弄得措手不及。

避免這個問題只需要小心點即可。有些Node.js 開發者因此養成了一個習慣,在所有呼叫回呼函數的語句前面加上一個return 關鍵字:

if(err) {	
    return done(err)	
}
登入後複製

在很多非同步函數裡,這種return 的回傳值都是沒有意義的,所以這種舉動只是為了簡單地避免這個錯誤而已。

錯誤3:深層嵌套的回調函數

深層嵌套的回呼函數通常被譽為“ 回調地獄”,它本身並不是什麼問題,但是它會導致程式碼很快變得失控:

function handleLogin(..., done) {	
    db.User.get(..., function(..., user) {	
        if(!user) {	
            return done(null, ‘failed to log in’)	
        }	
        utils.verifyPassword(..., function(..., okay) {	
            if(okay) {	
                return done(null, ‘failed to log in’)	
            }	
            session.login(..., function() {	
                done(null, ‘logged in’)	
            })	
        })	
    })	
}
登入後複製

越复杂的任务,这个的坏处就越大。像这样嵌套回调函数,我们的程序很容易出错,而且代码难以阅读和维护。一个权宜之计是把这些任务声明为一个个的小函数,然后再将它们联系起来。不过,(有可能是)最简便的解决方法之一是使用一个 Node.js 公共组件来处理这种异步 js,比如 Async.js:

function handleLogin(done) {	
    async.waterfall([	
        function(done) {	
            db.User.get(..., done)	
        },	
        function(user, done) {	
            if(!user) {	
            return done(null, ‘failed to log in’)	
            }	
            utils.verifyPassword(..., function(..., okay) {	
                done(null, user, okay)	
            })	
        },	
        function(user, okay, done) {	
            if(okay) {	
                return done(null, ‘failed to log in’)	
            }	
            session.login(..., function() {	
                done(null, ‘logged in’)	
            })	
        }	
    ], function() {	
        // ...	
    })	
}
登入後複製

Async.js 还提供了很多类似“async.waterfall” 的方法去处理不同的异步场景。为了简便起见,这里我们演示了一个简单的示例,实际情况往往复杂得多。

(打个广告,隔壁的《ES6 Generator 介绍》提及的 Generator 也是可以解决回调地狱的哦,而且结合 Promise 使用更加自然,请期待隔壁楼主的下篇文章吧:D)

错误4:期待回调函数同步执行

使用回调函数的异步程序不只是 JavaScript 和 Node.js 有,只是它们让这种异步程序变得流行起来。在其他编程语言里,我们习惯了两个语句一个接一个执行,除非两个语句之间有特殊的跳转指令。即使那样,这些还受限于条件语句、循环语句以及函数调用。

然而在 JavaScript 里,一个带有回调函数的方法直到回调完成之前可能都无法完成任务。当前函数会一直执行到底:

function testTimeout() {	
    console.log(“Begin”)	
    setTimeout(function() {	
        console.log(“Done!”)	
    }, duration * 1000)	
    console.log(“Waiting..”)	
}
登入後複製

你可能会注意到,调用“testTimeout” 函数会先输出“Begin”,然后输出“Waiting..”,紧接着几秒后输出“Done!”。

任何要在回调函数执行完后才执行的代码,都需要在回调函数里调用。

错误5:给“exports” 赋值,而不是“module.exports”

Node.js 认为每个文件都是一个独立的模块。如果你的包有两个文件,假设是“a.js” 和“b.js”,然后“b.js” 要使用“a.js” 的功能,“a.js” 必须要通过给 exports 对象增加属性来暴露这些功能:

// a.js	
exports.verifyPassword = function(user, password, done) { ... }
登入後複製

完成这步后,所有需要“a.js” 的都会获得一个带有“verifyPassword” 函数属性的对象:

// b.js	
require(‘a.js’) // { verifyPassword: function(user, password, done) { ... } }
登入後複製

然而,如果我们想直接暴露这个函数,而不是让它作为某些对象的属性呢?我们可以覆写 exports 来达到目的,但是我们绝对不能把它当做一个全局变量:

// a.js	
module.exports = function(user, password, done) { ... }
登入後複製

注意到我们是把“exports” 当做 module 对象的一个属性。“module.exports” 和“exports” 这之间区别是很重要的,而且经常会使 Node.js 新手踩坑。

错误6:从回调里抛出错误

JavaScript 有异常的概念。在语法上,学绝大多数传统语言(如 Java、C++)对异常的处理那样,JavaScript 可以抛出异常以及在 try-catch 语句块中捕获异常:

function slugifyUsername(username) {	
    if(typeof username === ‘string’) {	
        throw new TypeError(‘expected a string username, got &#39;+(typeof username))	
    }	
    // ...	
}	
try {	
    var usernameSlug = slugifyUsername(username)	
} catch(e) {	
    console.log(‘Oh no!’)	
}
登入後複製

然而,在异步环境下,tary-catch 可能不会像你所想的那样。比如说,如果你想用一个大的 try-catch 去保护一大段含有许多异步处理的代码,它可能不会正常的工作:

try {	
    db.User.get(userId, function(err, user) {	
        if(err) {	
            throw err	
        }	
        // ...	
        usernameSlug = slugifyUsername(user.username)	
        // ...	
    })	
} catch(e) {	
    console.log(‘Oh no!’)	
}
登入後複製

如果“db.User.get” 的回调函数异步执行了,那么 try-catch 原来所在的作用域就很难捕获到回调函数里抛出的异常了。

这就是为什么在 Node.js 里通常使用不同的方式处理错误,而且这使得所有回调函数的参数都需要遵循 (err, ...) 这种形式,其中第一个参数是错误发生时的 error 对象。

错误7:认为 Number 是一种整型数据格式

在 JavaScript 里数字都是浮点型,没有整型的数据格式。你可能认为这不是什么问题,因为数字大到溢出浮点型限制的情况很少出现。可实际上,当这种情况发生时就会出错。因为浮点数在表达一个整型数时只能表示到一个最大上限值,在计算中超过这个最大值时就会出问题。也许看起来有些奇怪,但在 Node.js 中下面代码的值是 true:

Math.pow(2, 53)+1 === Math.pow(2, 53)
登入後複製

很不幸的是,JavaScript 里有关数字的怪癖可还不止这些。尽管数字是浮点型的,但如下这种整数运算能正常工作:

5 % 2 === 1 // true	
5 &gt;&gt; 1 === 2 // true
登入後複製

然而和算术运算不同的是,位运算和移位运算只在小于 32 位最大值的数字上正常工作。例如,让“Math.pow(2, 53)” 位移 1 位总是得到 0,让其与 1 做位运算也总是得到 0:

Math.pow(2, 53) / 2 === Math.pow(2, 52) // true	
Math.pow(2, 53) &gt;&gt; 1 === 0 // true	
Math.pow(2, 53) | 1 === 0 // true
登入後複製

你可能极少会去处理如此大的数字,但如果你需要的话,有很多实现了大型精密数字运算的大整数库可以帮到你,比如 node-bigint。

错误8:忽略了流式 API 的优势

现在我们想创建一个简单的类代理 web 服务器,它能通过拉取其他 web 服务器的内容来响应和发起请求。作为例子,我们创建一个小型 web 服务器为 Gravatar 的图像服务。

var http = require(&#39;http&#39;)	
var crypto = require(&#39;crypto&#39;)	
http.createServer()	
.on(&#39;request&#39;, function(req, res) {	
    var email = req.url.substr(req.url.lastIndexOf(&#39;/&#39;)+1)	
    if(!email) {	
        res.writeHead(404)	
        return res.end()	
    }	
    var buf = new Buffer(1024*1024)	
    http.get(&#39;http://www.gravatar.com/avatar/&#39;+crypto.createHash(&#39;md5&#39;).update(email).digest(&#39;hex&#39;), function(resp) {	
        var size = 0	
        resp.on(&#39;data&#39;, function(chunk) {	
            chunk.copy(buf, size)	
            size += chunk.length	
        })	
        .on(&#39;end&#39;, function() {	
            res.write(buf.slice(0, size))	
            res.end()	
        })	
    })	
})	
.listen(8080)
登入後複製

在这个例子里,我们从 Gravatar 拉取图片,将它存进一个 Buffer 里,然后响应请求。如果 Gravatar 的图片都不是很大的话,这样做没问题。但想象下如果我们代理的内容大小有上千兆的话,更好的处理方式是下面这样:

http.createServer()	
.on(&#39;request&#39;, function(req, res) {	
    var email = req.url.substr(req.url.lastIndexOf(&#39;/&#39;)+1)	
    if(!email) {	
        res.writeHead(404)	
        return res.end()	
    }	
    http.get(&#39;http://www.gravatar.com/avatar/&#39;+crypto.createHash(&#39;md5&#39;).update(email).digest(&#39;hex&#39;), function(resp) {	
        resp.pipe(res)	
    })	
})	
.listen(8080)
登入後複製

这里我们只是拉取图片然后简单地以管道方式响应给客户端,而不需要在响应它之前读取完整的数据存入缓存。

错误9:出于 Debug 的目的使用 Console.log

在 Node.js 里,“console.log” 允许你打印任何东西到控制台上。比如传一个对象给它,它会以 JavaScript 对象的字符形式打印出来。它能接收任意多个的参数并将它们以空格作为分隔符打印出来。有很多的理由可以解释为什么开发者喜欢使用它来 debug 他的代码,然而我强烈建议你不要在实时代码里使用“console.log”。你应该要避免在所有代码里使用“console.log” 去 debug,而且应该在不需要它们的时候把它们注释掉。你可以使用一种专门做这种事的库代替,比如 debug。

这些库提供了便利的方式让你在启动程序的时候开启或关闭具体的 debug 模式,例如,使用 debug 的话,你能够阻止任何 debug 方法输出信息到终端上,只要不设置 DEBUG 环境变量即可。使用它十分简单:

// app.js	
var debug = require(‘debug’)(‘app’)	
debug(’Hello, %s!’, ‘world’)
登入後複製

开启 debug 模式只需简单地运行下面的代码把环境变量 DEBUG 设置到“app” 或“*” 上:

DEBUG=app node app.js
登入後複製

错误10:不使用监控程序

不管你的 Node.js 代码是跑在生产环境或是你的本地开发环境,一个能协调你程序的监控程序是十分值得拥有的。一条经常被开发者提及的,针对现代程序设计和开发的建议是你的代码应该有 <span class="pln">fail</span><span class="pun">-</span><span class="pln">fast</span> 机制。如果发生了一个意料之外的错误,不要尝试去处理它,而应该让你的程序崩溃然后让监控程序在几秒之内重启它。监控程序的好处不只是重启崩溃的程序,这些工具还能让你在程序文件发生改变的时候重启它,就像崩溃重启那样。这让开发 Node.js 程序变成了一个更加轻松愉快的体验。

Node.js 有太多的监控程序可以使用了,例如:

<span class="pln">pm2</span>

<span class="pln">forever</span>

<span class="pln">nodemon</span>

<span class="pln">supervisor</span>

所有这些工具都有它的优缺点。一些擅长于在一台机器上处理多个应用程序,而另一些擅长于日志管理。不管怎样,如果你想开始写一个程序,这些都是不错的选择。

总结

你可以看到,这其中的一些错误能给你的程序造成破坏性的影响,在你尝试使用 Node.js 实现一些很简单的功能时一些错误也可能会导致你受挫。即使 Node.js 已经使得新手上手十分简单,但它依然有些地方容易让人混乱。从其他语言过来的开发者可能已知道了这其中某些错误,但在 Node.js 新手里这些错误都是很常见的。幸运的是,它们都可以很容易地避免。我希望这个简短指南能帮助新手更好地编写 Node.js 代码,而且能够给我们大家开发出健壮高效的软件。

更多node相关知识,请访问:nodejs 教程!!

以上是nodejs有哪十大常見錯誤的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

一文聊聊Node中的記憶體控制 一文聊聊Node中的記憶體控制 Apr 26, 2023 pm 05:37 PM

基於無阻塞、事件驅動建立的Node服務,具有記憶體消耗低的優點,非常適合處理海量的網路請求。在海量請求的前提下,就需要考慮「記憶體控制」的相關問題了。 1. V8的垃圾回收機制與記憶體限制 Js由垃圾回收機

圖文詳解Node V8引擎的記憶體和GC 圖文詳解Node V8引擎的記憶體和GC Mar 29, 2023 pm 06:02 PM

這篇文章帶大家深入了解NodeJS V8引擎的記憶體和垃圾回收器(GC),希望對大家有幫助!

深入聊聊Node中的File模組 深入聊聊Node中的File模組 Apr 24, 2023 pm 05:49 PM

文件模組是對底層文件操作的封裝,例如文件讀寫/打開關閉/刪除添加等等文件模組最大的特點就是所有的方法都提供的**同步**和**異步**兩個版本,具有sync 字尾的方法都是同步方法,沒有的都是異

聊聊如何選擇一個最好的Node.js Docker映像? 聊聊如何選擇一個最好的Node.js Docker映像? Dec 13, 2022 pm 08:00 PM

選擇一個Node的Docker映像看起來像是小事,但是映像的大小和潛在漏洞可能會對你的CI/CD流程和安全造成重大的影響。那我們要如何選擇一個最好Node.js Docker映像呢?

Node.js 19正式發布,聊聊它的 6 大功能! Node.js 19正式發布,聊聊它的 6 大功能! Nov 16, 2022 pm 08:34 PM

Node 19已正式發布,以下這篇文章就來帶大家詳解了解Node.js 19的 6 大特性,希望對大家有幫助!

聊聊Node.js中的 GC (垃圾回收)機制 聊聊Node.js中的 GC (垃圾回收)機制 Nov 29, 2022 pm 08:44 PM

Node.js 是如何做 GC (垃圾回收)的?下面這篇文章就來帶大家了解一下。

一起聊聊Node中的事件循環 一起聊聊Node中的事件循環 Apr 11, 2023 pm 07:08 PM

事件循環是 Node.js 的基本組成部分,透過確保主執行緒不被阻塞來實現非同步編程,了解事件循環對建立高效應用程式至關重要。以下這篇文章就來帶大家深入了解Node中的事件循環 ,希望對大家有幫助!

node無法用npm指令怎麼辦 node無法用npm指令怎麼辦 Feb 08, 2023 am 10:09 AM

node無法用npm指令是因為沒有正確配置環境變量,其解決方法是:1、開啟“系統屬性”;2、找到“環境變數”->“系統變數”,然後編輯環境變數;3、找到nodejs所在的資料夾;4、點選「確定」即可。

See all articles