ホームページ >ウェブフロントエンド >フロントエンドQ&A >Nodejs でよくあるエラーのトップ 10 は何ですか?

Nodejs でよくあるエラーのトップ 10 は何ですか?

青灯夜游
青灯夜游オリジナル
2022-01-13 17:14:282361ブラウズ

ノードでよくある 10 の間違い: 1. イベント ループのブロック; 2. コールバック関数を複数回呼び出す; 3. 深くネストされたコールバック関数; 4. コールバック関数が同期的に実行されることを期待する; 5. "exports" を与える代入; 6. コールバックからエラーをスローする; 7. Number は整数データ形式であると考える; 8. ストリーミング API などの利点を無視する

Nodejs でよくあるエラーのトップ 10 は何ですか?

#このチュートリアルの動作環境: Windows7 システム、nodejs バージョン 12.19.0、DELL G3 コンピューター。

Node.js は登場以来、多くの賞賛と批判を受けてきました。この議論は今後も続きますが、すぐに終わることはありません。こうした議論では、すべての言語とプラットフォームがいくつかの中核的な問題に基づいて批判されており、それがこれらのプラットフォームの使用方法であるということを見落としがちです。 Node.js で信頼性の高いコードを書くのがどれほど難しくても、同時実行性の高いコードを書くのがどれほど簡単であっても、このプラットフォームはしばらく前から存在しており、堅牢で複雑な Web サービスを多数作成するために使用されてきました。これらの Web サービスは、拡張性に優れているだけでなく、インターネット上で存続するまでにその堅牢性を証明します。

ただし、他のプラットフォームと同様、Node.js では開発者が間違いを犯しやすくなります。これらのエラーの中には、プログラムのパフォーマンスを低下させるものや、Node.js を使用できなくなるものもあります。この記事では、Node.js の初心者が犯しやすい 10 の間違いと、それらを回避する方法について説明します。

エラー 1: イベント ループのブロック<span class="pun"></span>Node.js の JavaScript は (ブラウザーと同様に) シングルスレッド環境を提供します。これは、プログラムで 2 つのことを同時に実行するのではなく、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 サーバーが数千のユーザーに同時にサービスを提供しようとしている状況では、問題が発生する可能性があります。

このユーザー配列がデータベースから取得される場合、理想的な解決策は、並べ替えられたデータをデータベースから取り出すことです。イベント ループが金融取引データの履歴合計を計算するループによってブロックされている場合、イベント ループが拘束されるのを避けるために、計算ループをイベント ループの外側のキューにプッシュする必要があります。

ご覧のとおり、このタイプのエラーを修正する特効薬はなく、状況ごとに個別の解決策があるだけです。基本的な考え方は、同時クライアント接続を処理する 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 ステートメントをコメントアウトして、文字列以外のパスワードをこの関数に渡しても、結局は 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 でよくあるエラーのトップ 10 は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。