一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

NodeJS 開(kāi)發(fā)者的 10 個(gè)常見(jiàn)錯(cuò)誤

 慈溪全媒體 2015-06-19

 


自 Node.js 公諸于世的那一刻,就伴隨著贊揚(yáng)和批評(píng)的聲音。這個(gè)爭(zhēng)論仍在持續(xù),而且并不會(huì)很快消失。而我們常常忽略掉這些爭(zhēng)論產(chǎn)生的原因,每種編程語(yǔ)言和平臺(tái)都是因某些問(wèn)題而受到批評(píng),而這些問(wèn)題的產(chǎn)生,是取決于我們?nèi)绾问褂眠@個(gè)平臺(tái)。不管有多難才能寫出安全的 Node.js 代碼,或有多容易寫出高并發(fā)的代碼,該平臺(tái)已經(jīng)有相當(dāng)長(zhǎng)一段時(shí)間,并已被用來(lái)建立一個(gè)數(shù)量龐大、穩(wěn)健和成熟的 web 服務(wù)器。這些 web 服務(wù)器伸縮性強(qiáng),并且它們通過(guò)在 Internet 上穩(wěn)定的運(yùn)行時(shí)間,證明自己的穩(wěn)定性。


然而,像其它平臺(tái)一樣,Node.js 容易因開(kāi)發(fā)者問(wèn)題而受到批評(píng)。一些錯(cuò)誤會(huì)降低性能,而其它一些問(wèn)題會(huì)讓 Node.js 直接崩潰。在這篇文章里,我們將會(huì)聊一聊關(guān)于 Node.js 新手的 10 個(gè)常犯錯(cuò)誤,并讓他們知道如何避免這些錯(cuò)誤,從而成為一名 Node.js 高手。




錯(cuò)誤 #1:阻塞事件循環(huán)


JavaScript 在 Node.js (就像在瀏覽器一樣) 提供單線程執(zhí)行環(huán)境。這意味著你的程序不能同時(shí)執(zhí)行兩部分代碼,但能通過(guò) I/O 綁定異步回調(diào)函數(shù)實(shí)現(xiàn)并發(fā)。例如:一個(gè)來(lái)自Node.js 的請(qǐng)求是到數(shù)據(jù)庫(kù)引擎獲取一些文檔,在這同時(shí)允許 Node.js 專注于應(yīng)用程序其它部分:


// 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..

// 嘗試從數(shù)據(jù)庫(kù)中獲取一個(gè)用戶對(duì)象。在這個(gè)函數(shù)執(zhí)行的一刻,Node.js 有空去運(yùn)行代碼其它部分..

db.User.get(userId, function(err, user) {

// .. until the moment the user object has been retrieved here

// .. 直到用戶對(duì)象檢索到這里的那一刻

})



然而,具有計(jì)算密集型代碼的 Node.js 實(shí)例被數(shù)以萬(wàn)計(jì)客戶端同時(shí)連接執(zhí)行時(shí),會(huì)導(dǎo)致阻塞事件循環(huán),并使所有客戶端處于等待響應(yīng)狀態(tài)。計(jì)算密集型代碼,包括嘗試給一個(gè)龐大數(shù)組進(jìn)行排序操作和運(yùn)行一個(gè)格外長(zhǎng)的循環(huán)等。例如:


function sortUsersByAge(users) {

users.sort(function(a, b) {

return a.age > b.age ? -1 : 1

})

}


基于小 “users” 數(shù)組執(zhí)行 “sortUserByAge” 函數(shù),可能沒(méi)什么問(wèn)題,當(dāng)基于龐大數(shù)組時(shí),會(huì)嚴(yán)重影響整體性能。如果在不得不這樣操作的情況下,你必須確保程序除了等待事件循環(huán)而別無(wú)他事(例如,用 Node.js 建立命令行工具的一部分,整個(gè)東西同步運(yùn)行是沒(méi)問(wèn)題的),然后這可能沒(méi)問(wèn)題。然而,在 Node.js 服務(wù)器實(shí)例嘗試同時(shí)服務(wù)成千上萬(wàn)個(gè)用戶的情況下,這將是一個(gè)毀滅性的問(wèn)題。


如果用戶數(shù)組是從數(shù)據(jù)庫(kù)檢索出來(lái)的,有個(gè)解決辦法是,先在數(shù)據(jù)庫(kù)中排序,然后再直接檢索。如果因需要計(jì)算龐大的金融交易歷史數(shù)據(jù)總和,而造成阻塞事件循環(huán),這可以創(chuàng)建額外的worker / queue 來(lái)避免阻塞事件循環(huán)。


正如你所看到的,這沒(méi)有新技術(shù)來(lái)解決這類 Node.js 問(wèn)題,而每種情況都需要單獨(dú)處理。而基本解決思路是:不要讓 Node.js 實(shí)例的主線程執(zhí)行 CPU 密集型工作 – 客戶端同時(shí)鏈接時(shí)。


錯(cuò)誤 #2:調(diào)用回調(diào)函數(shù)多于一次


JavaScript 一直都是依賴于回調(diào)函數(shù)。在瀏覽器中,處理事件是通過(guò)調(diào)用函數(shù)(通常是匿名的),這個(gè)動(dòng)作如同回調(diào)函數(shù)。Node.js 在引進(jìn) promises 之前,回調(diào)函數(shù)是異步元素用來(lái)互相連接對(duì)方的唯一方式 。現(xiàn)在回調(diào)函數(shù)仍被使用,并且包開(kāi)發(fā)者仍然圍繞著回調(diào)函數(shù)設(shè)計(jì) APIs。一個(gè)關(guān)于使用回調(diào)函數(shù)的常見(jiàn) Node.js 問(wèn)題是:不止一次調(diào)用。通常情況下,一個(gè)包提供一個(gè)函數(shù)去異步處理一些東西,設(shè)計(jì)出來(lái)是期待有一個(gè)函數(shù)作為最后一個(gè)參數(shù),當(dāng)異步任務(wù)完成時(shí)就會(huì)被調(diào)用:


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)

})

}


注意每次調(diào)用 “done” 都有一個(gè)返回語(yǔ)句(return),而最后一個(gè) “done” 則可省略返回語(yǔ)句。這是因?yàn)檎{(diào)用回調(diào)函數(shù)后,并不會(huì)自動(dòng)結(jié)束當(dāng)前執(zhí)行函數(shù)。如果第一個(gè) “return” 注釋掉,然后給這個(gè)函數(shù)傳進(jìn)一個(gè)非字符串密碼,導(dǎo)致 “computeHash” 仍然會(huì)被調(diào)用。這取決于 “computeHash” 如何處理這樣一種情況,“done” 可能會(huì)調(diào)用多次。任何一個(gè)人在別處使用這個(gè)函數(shù)可能會(huì)變得措手不及,因?yàn)樗鼈儌鬟M(jìn)的該回調(diào)函數(shù)被多次調(diào)用。


只要小心就可以避免這個(gè) Node.js 錯(cuò)誤。而一些 Node.js 開(kāi)發(fā)者養(yǎng)成一個(gè)習(xí)慣是:在每個(gè)回調(diào)函數(shù)調(diào)用前添加一個(gè) return 關(guān)鍵字。


if(err) {

return done(err)

}


對(duì)于許多異步函數(shù),它的返回值幾乎是無(wú)意義的,所以該方法能讓你很好地避免這個(gè)問(wèn)題。


錯(cuò)誤 #3:函數(shù)嵌套過(guò)深


函數(shù)嵌套過(guò)深,時(shí)常被稱為“回調(diào)函數(shù)地獄”,但這并不是 Node.js 自身問(wèn)題。然而,這會(huì)導(dǎo)致一個(gè)問(wèn)題:代碼很快失去控制。


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’)

})

})

})

}



任務(wù)有多復(fù)雜,代碼就有多糟糕。以這種方式嵌套回調(diào)函數(shù),我們很容易就會(huì)碰到問(wèn)題而崩潰,并且難以閱讀和維護(hù)代碼。一種替代方式是以函數(shù)聲明這些任務(wù),然后將它們連接起來(lái)。盡管,有一種最干凈的方法之一 (有爭(zhēng)議的)是使用 Node.js 工具包,它專門處理異步 JavaScript 模式,例如 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.waterfall”,Async.js 提供了很多其它函數(shù)來(lái)解決不同的異步模式。為了簡(jiǎn)潔,我們?cè)谶@里使用一個(gè)較為簡(jiǎn)單的案例,但實(shí)際情況往往更糟。


錯(cuò)誤 #4:期望回調(diào)函數(shù)以同步方式運(yùn)行


異步程序的回調(diào)函數(shù)并不是 JavaScript 和 Node.js 獨(dú)有的,但它們是造成回調(diào)函數(shù)流行的原因。而對(duì)于其它編程語(yǔ)言,我們潛意識(shí)地認(rèn)為執(zhí)行順序是一步接一步的,如兩個(gè)語(yǔ)句將會(huì)執(zhí)行完第一句再執(zhí)行第二句,除非這兩個(gè)語(yǔ)句間有一個(gè)明確的跳轉(zhuǎn)語(yǔ)句。盡管那樣,它們經(jīng)常局限于條件語(yǔ)句、循環(huán)語(yǔ)句和函數(shù)調(diào)用。


然而,在 JavaScript 中,回調(diào)某個(gè)特定函數(shù)可能并不會(huì)立刻運(yùn)行,而是等到任務(wù)完成后才運(yùn)行。下面例子就是直到?jīng)]有任何任務(wù),當(dāng)前函數(shù)才運(yùn)行:


function testTimeout() {

console.log(“Begin”)

setTimeout(function() {

console.log(“Done!”)

}, duration * 1000)

console.log(“Waiting..”)

}


你會(huì)注意到,調(diào)用 “testTimeout” 函數(shù)會(huì)首先打印 “Begin”,然后打印 “Waiting..”,緊接大約一秒后才打印 “Done!”。


任何一個(gè)需要在回調(diào)函數(shù)被觸發(fā)后執(zhí)行的東西,都要把它放在回調(diào)函數(shù)內(nèi)。


錯(cuò)誤 #5:用“exports”,而不是“module.exports”


Node.js 將每個(gè)文件視為一個(gè)孤立的小模塊。如果你的包(package)含有兩個(gè)文件,或許是 “a.js” 和 “b.js”。因?yàn)?“b.js” 要獲取 “a.js” 的功能,所以 “a.js” 必須通過(guò)為 exports 對(duì)象添加屬性來(lái)導(dǎo)出它。


// a.js

exports.verifyPassword = function(user, password, done) { ... }


當(dāng)這樣操作后,任何引入 “a.js” 模塊的文件將會(huì)得到一個(gè)帶有屬性方法 “verifyPassword” 的對(duì)象:


// b.js

require(‘a(chǎn).js’)

// { verifyPassword: function(user, password, done) { ... } }


然而,如果我們想直接導(dǎo)出這個(gè)函數(shù),而不是作為某個(gè)對(duì)象的屬性呢?我們能通過(guò)覆蓋 exports 對(duì)象來(lái)達(dá)到這個(gè)目的,但我們不能將它視為一個(gè)全局變量:


// a.js

module.exports = function(user, password, done) { ... }


注意,我們是如何將 “exports” 作為 module 對(duì)象的一個(gè)屬性。在這里知道 “module.exports” 和 “exports” 之間區(qū)別是非常重要的,并且這經(jīng)常會(huì)導(dǎo)致 Node.js 開(kāi)發(fā)新手們產(chǎn)生挫敗感。


錯(cuò)誤 #6:在回調(diào)函數(shù)內(nèi)拋出錯(cuò)誤


JavaScript 有個(gè)“異常”概念。異常處理與大多數(shù)傳統(tǒng)語(yǔ)言的語(yǔ)法類似,例如 Java 和 C++,JavaScript 能在 try-catch 塊內(nèi) “拋出(throw)” 和 捕捉(catch)異常:


function slugifyUsername(username) {

if(typeof username === ‘string’) {

throw new TypeError(‘expected a string username, got '+(typeof username))

}

// ...

}


try {

var usernameSlug = slugifyUsername(username)

} catch(e) {

console.log(‘Oh no!’)

}


然而,如果你把 try-catch 放在異步函數(shù)內(nèi),它會(huì)出乎你意料,它并不會(huì)執(zhí)行。例如,如果你想保護(hù)一段含有很多異步活動(dòng)的代碼,而且這段代碼包含在一個(gè) try-catch 塊內(nèi),而結(jié)果是:它不一定會(huì)運(yùn)行。


try {

db.User.get(userId, function(err, user) {

if(err) {

throw err

}

// ...

usernameSlug = slugifyUsername(user.username)

// ...

})

} catch(e) {

console.log(‘Oh no!’)

}


如果回調(diào)函數(shù) “db.User.get” 異步觸發(fā)了,雖然作用域里包含的 try-catch 塊離開(kāi)了上下文,仍然能捕捉那些在回調(diào)函數(shù)的拋出的錯(cuò)誤。


這就是 Node.js 中如何處理錯(cuò)誤的另外一種方式。另外,有必要遵循所有回調(diào)函數(shù)的參數(shù)(err, …)模式,所有回調(diào)函數(shù)的第一個(gè)參數(shù)期待是一個(gè)錯(cuò)誤對(duì)象。


錯(cuò)誤 #7:認(rèn)為數(shù)字是整型


數(shù)字在 JavaScript 中都是浮點(diǎn)型,JS 沒(méi)有整型。你可能不能預(yù)料到這將是一個(gè)問(wèn)題,因?yàn)閿?shù)大到超出浮點(diǎn)型范圍的情況并不常見(jiàn)。


Math.pow(2, 53)+1 === Math.pow(2, 53)


不幸的是,在 JavaScript 中,這種關(guān)于數(shù)字的怪異情況遠(yuǎn)不止于此。盡管數(shù)字都是浮點(diǎn)型,對(duì)于下面的表達(dá)式,操作符對(duì)于整型也能正常運(yùn)行:


5 >> 1 === 2

// true


然而,不像算術(shù)運(yùn)算符那樣,位操作符和位移操作符只能操作后 32 位,如同 “整型” 數(shù)。例如,嘗試位移 “Math.pow(2,53)” 1 位,會(huì)得到結(jié)果 0。嘗試與 1 進(jìn)行按位或運(yùn)算,得到結(jié)果 1。


Math.pow(2, 53) / 2 === Math.pow(2, 52)

// true

Math.pow(2, 53) >> 1 === 0

// true

Math.pow(2, 53) | 1 === 1

// true


你可能很少需要處理很大的數(shù),但如果你真的要處理的話,有很多大整型庫(kù)能對(duì)大型精度數(shù)完成重要的數(shù)學(xué)運(yùn)算,如 node-bigint。


錯(cuò)誤 #8:忽略了 Streaming(流) API 的優(yōu)勢(shì)


大家都說(shuō)想建立一個(gè)小型代理服務(wù)器,它能響應(yīng)從其它服務(wù)器獲取內(nèi)容的請(qǐng)求。作為一個(gè)案例,我們將建立一個(gè)供應(yīng) Gravatar 圖像的小型 Web 服務(wù)器:


var http = require('http')

var crypto = require('crypto')


http.createServer()

.on('request', function(req, res) {

var email = req.url.substr(req.url.lastIndexOf('/')+1)

if(!email) {

res.writeHead(404)

return res.end()

}


var buf = new Buffer(1024*1024)

http.get('http://www./avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) {

var size = 0

resp.on('data', function(chunk) {

chunk.copy(buf, size)

size += chunk.length

})

.on('end', function() {

res.write(buf.slice(0, size))

res.end()

})

})

})

.listen(8080)


在這個(gè)特殊例子中有一個(gè) Node.js 問(wèn)題,我們從 Gravatar 獲取圖像,將它讀進(jìn)緩存區(qū),然后響應(yīng)請(qǐng)求。這不是一個(gè)多么糟糕的問(wèn)題,因?yàn)?Gravatar 返回的圖像并不是很大。然而,想象一下,如果我們代理的內(nèi)容大小有成千上萬(wàn)兆。那就有一個(gè)更好的方法了:


http.createServer()

.on('request', function(req, res) {

var email = req.url.substr(req.url.lastIndexOf('/')+1)

if(!email) {

res.writeHead(404)

return res.end()

}


http.get('http://www./avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) {

resp.pipe(res)

})

})

.listen(8080)


這里,我們獲取圖像,并簡(jiǎn)單地通過(guò)管道響應(yīng)給客戶端。絕不需要我們?cè)陧憫?yīng)之前,將全部?jī)?nèi)容讀取到緩沖區(qū)。


錯(cuò)誤 #9:把 Console.log 用于調(diào)試目的


在 Node.js 中,“console.log” 允許你向控制臺(tái)打印幾乎所有東西。傳遞一個(gè)對(duì)象給它,它會(huì)以 JavaScript 對(duì)象字面量的方式打印出來(lái)。它接受任意多個(gè)參數(shù),并以空格作為分隔符打印它們。有許多個(gè)理由讓開(kāi)發(fā)者很想用這個(gè)來(lái)調(diào)試(debug)自己的代碼;然而,我強(qiáng)烈建議你避免在真正程序里使用 “console.log” 。你應(yīng)該避免在全部代碼里使用 “console.log” 進(jìn)行調(diào)試(debug),當(dāng)不需要它們的時(shí)候,應(yīng)注釋掉它們。相反,使用專門為調(diào)試建立的庫(kù),如:debug。


當(dāng)你開(kāi)始編寫應(yīng)用程序時(shí),這些庫(kù)能方便地啟動(dòng)和禁用某行調(diào)試(debug)功能。例如,通過(guò)不設(shè)置 DEBUG 環(huán)境變量,能夠防止所有調(diào)試行被打印到終端。使用它很簡(jiǎn)單:


// app.js

var debug = require(‘debug’)(‘a(chǎn)pp’)

debug(’Hello, %s!’, ‘world’)


為了啟動(dòng)調(diào)試行,將環(huán)境變量 DEBUG 設(shè)置為 “app” 或 “*”,就能簡(jiǎn)單地運(yùn)行這些代碼了:


DEBUG=app node app.js


錯(cuò)誤 #10:不使用管理程序


不管你的 Node.js 代碼運(yùn)行在生產(chǎn)環(huán)境還是本地開(kāi)發(fā)環(huán)境,一個(gè)監(jiān)控管理程序能很好地管理你的程序,所以它是一個(gè)非常有用并值得擁有的東西。開(kāi)發(fā)者設(shè)計(jì)和實(shí)現(xiàn)現(xiàn)代應(yīng)用時(shí)常常推薦的一個(gè)最佳實(shí)踐是:快速失敗,快速迭代。


如果發(fā)生一個(gè)意料之外的錯(cuò)誤,不要試圖去處理它,而是讓你的程序崩潰,并有個(gè)監(jiān)控者在幾秒后重啟它。管理程序的好處不止是重啟崩潰的程序。這個(gè)工具允許你重啟崩潰的程序的同時(shí),也允許文件發(fā)生改變時(shí)重啟程序。這讓開(kāi)發(fā) Node.js 程序變成一段更愉快的體驗(yàn)。


有很多 Node.js 可用的管理程序。例如:


  • pm2

  • forever

  • nodemon

  • supervisor


所有這些工具各有優(yōu)劣。一些有利于在同一個(gè)機(jī)器里處理多個(gè)應(yīng)用程序,而其它擅長(zhǎng)于日志管理。然而,如果你想開(kāi)始使用這些程序,它們都是很好的選擇。


總結(jié)


正如你所知道的那樣,一些 Node.js 問(wèn)題能對(duì)你的程序造成毀滅性打擊。而一些則會(huì)在你嘗試完成最簡(jiǎn)單的東西時(shí),讓你產(chǎn)生挫敗感。盡管 Node.js 的開(kāi)發(fā)門檻較低,但它仍然有很容易搞混的地方。從其它編程語(yǔ)言轉(zhuǎn)過(guò)來(lái)學(xué)習(xí) Node.js 開(kāi)發(fā)者可能會(huì)遇到這些問(wèn)題,但這些錯(cuò)誤在 Node.js 新手中也是十分常見(jiàn)的。幸運(yùn)的是,它們很容易避免。我希望這個(gè)簡(jiǎn)短指導(dǎo)能幫助 Node.js 新手寫出更優(yōu)秀的代碼,并為我們開(kāi)發(fā)出穩(wěn)定高效的軟件。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    一区二区三区在线不卡免费| 欧美日韩综合综合久久久| 日本加勒比中文在线观看| 日韩中文字幕欧美亚洲| 中文字幕一区二区免费| 在线免费观看一二区视频| 日本在线高清精品人妻| 日韩人妻有码一区二区| 国产91人妻精品一区二区三区| 亚洲一区二区精品久久av| 日韩在线中文字幕不卡| 日韩欧美国产高清在线| 欧美成人免费视频午夜色| 欧美精品二区中文乱码字幕高清| 亚洲欧美日本视频一区二区| 亚洲欧美中文字幕精品| 午夜精品一区免费视频| 国产欧美一区二区三区精品视 | 日韩免费午夜福利视频| 国产午夜福利一区二区| 五月婷婷亚洲综合一区| 太香蕉久久国产精品视频| 久草视频这里只是精品| 日本淫片一区二区三区| 色丁香之五月婷婷开心| 欧美在线观看视频免费不卡| 日本人妻中出在线观看| 日韩国产亚洲欧美另类 | 欧美精品在线观看国产| 亚洲伦理中文字幕在线观看| 开心久久综合激情五月天| 日韩精品一区二区三区含羞含羞草 | 老外那个很粗大做起来很爽| 99久只有精品免费视频播放| 肥白女人日韩中文视频| 日韩一区二区免费在线观看| 亚洲精品欧美精品一区三区| 视频在线免费观看你懂的| 91人妻人人做人碰人人九色| 日韩一区二区三区在线欧洲| 日韩人妻精品免费一区二区三区|