Siitä hetkestä lähtien, kun Node.js paljastettiin maailmalle, se on nähnyt kohtuullisen osuuden molemmista kiitosta ja kritiikkiä . Keskustelu jatkuu edelleen, eikä se välttämättä pääty milloinkaan pian. Näissä keskusteluissa jätämme usein huomiotta sen, että jokaista ohjelmointikieliä ja -alustaa kritisoidaan tiettyjen asioiden perusteella, jotka syntyvät alustan käytöstä. Riippumatta siitä, kuinka vaikeaa Node.js tekee turvallisen koodin kirjoittamisesta ja kuinka helppoa se tekee erittäin samanaikaisen koodin kirjoittamisen, foorumi on ollut käytössä jo jonkin aikaa, ja sitä on käytetty valtavan määrän vankkojen ja hienostuneiden verkkopalvelujen rakentamiseen. Nämä verkkopalvelut ovat mittakaavassa hyvin ja ovat osoittaneet vakautensa kestämällä aikaa Internetissä.
Kuitenkin, kuten mikä tahansa muu alusta, Node.js on altis kehittäjien ongelmille ja ongelmille. Jotkut näistä virheistä heikentävät suorituskykyä, kun taas toiset saavat Node.js: n näyttämään suoraan käyttökelvottomalta mitä tahansa varten. Tässä artikkelissa tarkastellaan kymmenen yleistä virhettä, joita Node.js: n uudet kehittäjät tekevät usein, ja miten niistä voidaan välttyä Node.js pro .
Node.js: n JavaScripti (aivan kuten selaimessa) tarjoaa yhden kierteisen ympäristön. Tämä tarkoittaa, että sovelluksessasi ei ole kahta osaa, jotka toimivat rinnakkain; sen sijaan samanaikaisuus saavutetaan käsittelemällä I / O-sidottuja operaatioita asynkronisesti. Esimerkiksi Node.js: n pyyntö tietokantamoottorille jonkin asiakirjan noutamiseksi antaa Node.js: lle mahdollisuuden keskittyä johonkin muuhun sovelluksen osaan:
// 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-ilmentymässä oleva tuhansia asiakkaita yhdistävä CPU-sidottu koodi on kuitenkin kaikki, mitä tarvitaan tapahtumasilmukan estämiseen, jolloin kaikki asiakkaat odottavat. Suorittimeen sidotut koodit sisältävät suuren ryhmän järjestämisen, äärimmäisen pitkän silmukan suorittamisen ja niin edelleen. Esimerkiksi:
function sortUsersByAge(users) { users.sort(function(a, b) { return a.age Tämän 'sortUsersByAge' -toiminnon kutsuminen voi olla hienoa, jos se suoritetaan pienellä 'käyttäjien' matriisilla, mutta suurella taulukolla sillä on kamala vaikutus yleiseen suorituskykyyn. Jos tämä on ehdottomasti tehtävä, ja olet varma, että tapahtumasilmukassa ei ole mitään muuta odottavaa (esimerkiksi, jos tämä oli osa komentorivityökalua, jonka rakennat Node.js: llä, ja ei olisi väliä, jos koko asia suoritettaisiin synkronisesti), niin tämä ei välttämättä ole ongelma. Node.js-palvelinilmentymässä, joka yrittää palvella tuhansia käyttäjiä kerrallaan, tällainen malli voi kuitenkin osoittautua kohtalokkaaksi.
Jos tätä joukkoa käyttäjiä haettiin tietokannasta, ihanteellinen ratkaisu olisi hakea se jo lajiteltuina suoraan tietokannasta. Jos tapahtumasilmukka oli estetty silmukalla, joka on kirjoitettu laskemaan finanssitransaktiotietojen pitkä historia, se voidaan siirtää jollekin ulkopuoliselle työntekijälle / jonoasetukselle tapahtumasilmukan tukkeutumisen välttämiseksi.
Kuten näette, tällaiselle Node.js-ongelmalle ei ole hopea-bullet-ratkaisua, vaan jokainen tapaus on käsiteltävä erikseen. Perusajatuksena on olla suorittamatta intensiivistä prosessorityötä Node.js-etupuolella - niihin, joihin asiakkaat muodostavat yhteyden samanaikaisesti.
Virhe # 2: Soittopyynnön kutsuminen enemmän kuin kerran
JavaScript on luottanut takaisinsoittoihin ikuisesti. Web-selaimissa tapahtumat hoidetaan välittämällä viittauksia (usein nimettömiin) toimintoihin, jotka toimivat takaisinsoittona. Node.js: ssä soittopyynnöt olivat aiemmin ainoa tapa, jolla koodisi asynkroniset elementit kommunikoivat keskenään - kunnes lupaukset on annettu. Soittopyynnöt ovat edelleen käytössä, ja pakettikehittäjät suunnittelevat edelleen API: nsa takaisinsoittojen ympärille. Yksi yleinen takaisinsoittojen käyttöön liittyvä Node.js-ongelma on soittaa heille useammin kuin kerran. Tyypillisesti paketin tarjoama funktio tehdä jotain asynkronisesti on suunniteltu odottamaan funktiota sen viimeisenä argumenttina, jota kutsutaan, kun asynkroninen tehtävä on suoritettu:
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) }) }
Huomaa, kuinka paluulauseke on joka kerta, kun 'valmis' kutsutaan, viimeiseen kertaan asti. Tämä johtuu siitä, että soittopyyntö ei lopeta automaattisesti nykyisen toiminnon suorittamista. Jos ensimmäinen palautus kommentoitiin, merkkijonottoman salasanan välittäminen tälle toiminnolle johtaa edelleen 'computeHash' -viestiin. Riippuen siitä, miten ”computeHash” käsittelee tällaista skenaariota, ”valmiiksi” voidaan kutsua useita kertoja. Kuka tahansa, joka käyttää tätä toimintoa muualta, voi jäädä täysin varattomaksi, kun heidän soittamaansa soittopyyntöön vedotaan useita kertoja.
Varovaisuus riittää tämän Node.js -virheen välttämiseksi. Jotkut Node.js-kehittäjät käyttävät tapaa lisätä palautusavainsana ennen jokaista soittopyyntöä:
if(err) { return done(err) }
Palautusarvolla ei ole melkein merkitystä monissa asynkronisissa toiminnoissa, joten tämän lähestymistavan avulla on usein helppo välttää tällainen ongelma.
Virhe # 3: Soita takaisin
Syvään sisäkkäin tulevat soittopyynnöt, joita usein kutsutaan 'takaisinsoittohelveksiksi', eivät ole sinänsä Node.js-ongelma. Tämä voi kuitenkin aiheuttaa ongelmia koodin nopeassa hallitsemisessa:
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’) }) }) }) }

Mitä monimutkaisempi tehtävä on, sitä huonommin tämä voi saada. Pesimällä takaisinsoittoja tällä tavalla päädymme helposti virheiden alaisiksi, vaikeasti luettaviksi ja vaikeasti ylläpidettäviksi koodeiksi. Yksi ratkaisu on julistaa nämä tehtävät pieniksi toiminnoiksi ja linkittää ne sitten yhteen. Vaikka yksi (epäilemättä) puhtaimmista ratkaisuista on käyttää Node.js-apuohjelmaa, joka käsittelee asynkronisia JavaScript-malleja, kuten 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() { // ... }) }
Samoin kuin 'asynk. Vesiputous', on olemassa useita muita toimintoja, jotka Async.js tarjoaa käsittelemään erilaisia asynkronisia malleja. Lyhyesti sanottuna käytimme tässä yksinkertaisempia esimerkkejä, mutta todellisuus on usein huonompi.
Virhe # 4: Takaisinsoittojen odotetaan toimivan synkronisesti
Asynkroninen ohjelmointi takaisinsoittojen kanssa ei välttämättä ole jotain ainutlaatuista JavaScriptille ja Node.js: lle, mutta ne ovat vastuussa sen suosiosta. Muiden ohjelmointikielien kanssa olemme tottuneet ennustettavaan suoritusjärjestykseen, jossa kaksi käskyä suoritetaan peräkkäin, ellei ole erityistä käskyä hypätä lauseiden välillä. Silloinkin nämä rajoittuvat usein ehdollisiin lauseisiin, silmukka-lauseisiin ja funktiokutsuihin.
JavaScriptiä käytettäessä tietty toiminto ei kuitenkaan välttämättä toimi oikein ennen vastauksen suorittamista. Nykyisen toiminnon suorittaminen jatkuu loppuun asti ilman pysähdyksiä:
function testTimeout() { console.log(“Begin”) setTimeout(function() { console.log(“Done!”) }, duration * 1000) console.log(“Waiting..”) }
Kuten huomaat, 'testTimeout' -toiminnon kutsuminen tulostaa ensin 'Aloita', sitten 'Odottaa ..' ja sen jälkeen viestin 'Valmis!' noin sekunnin kuluttua.
Kaikelle, mitä täytyy tapahtua soittopyynnön jälkeen, on vedettävä sen sisältä.
Virhe # 5: Määritetään 'viennille', 'moduuli.vienti' sijaan
Node.js käsittelee kutakin tiedostoa pienenä eristettynä moduulina. Jos paketissasi on kaksi tiedostoa, ehkä 'a.js' ja 'b.js', jotta 'b.js' voi käyttää 'a.js': n toimintoja, 'a.js' täytyy viedä se lisäämällä ominaisuuksia vientiobjekti:
// a.js exports.verifyPassword = function(user, password, done) { ... }
Kun tämä on tehty, kaikille, jotka tarvitsevat “a.js”, annetaan objekti, jonka ominaisuusfunktio on “verifyPassword”:
// b.js require(‘a.js’) // { verifyPassword: function(user, password, done) { ... } }
Entä jos haluamme viedä tämän toiminnon suoraan, emmekä jonkun objektin omaisuudeksi? Voimme korvata viennin tätä varten, mutta emme saa käsitellä sitä globaalina muuttujana:
// a.js module.exports = function(user, password, done) { ... }
Huomaa, miten käsittelemme 'vientiä' moduuliobjektin ominaisuutena. Ero 'moduuli.vienti' ja 'vienti' välillä on erittäin tärkeä, ja se aiheuttaa usein turhautumista uusien Node.js-kehittäjien keskuudessa.
Virhe # 6: Heittovirheet sisäisistä soittopyynnöistä
JavaScriptillä on käsite poikkeuksista. Jäljittelemällä melkein kaikkien perinteisten kielten syntaksia lukuun ottamatta Java: n ja C ++: n käsittelytukea, JavaScripti voi 'heittää' ja tarttua poikkeuksiin try-catch-lohkoissa:
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 ei kuitenkaan toimi niin kuin voit odottaa sen asynkronisissa tilanteissa. Esimerkiksi, jos haluat suojata suuren koodinpalan, jossa on paljon asynkronista toimintaa, yhdellä isolla try-catch-lohkolla, se ei välttämättä toimi:
try { db.User.get(userId, function(err, user) { if(err) { throw err } // ... usernameSlug = slugifyUsername(user.username) // ... }) } catch(e) { console.log(‘Oh no!’) }
Jos soittopyyntö db.User.get-sovellukseen laukaisi asynkronisesti, try-catch-lohkon sisältävä laajuus olisi jo pitkään mennyt kontekstista, jotta se pystyisi edelleen saamaan takaisinsoiton sisälle heitetyt virheet.
Näin virheitä käsitellään eri tavalla Node.js: ssä, minkä vuoksi on välttämätöntä noudattaa (err,…) -mallia kaikissa soittopyyntöfunktioissa - kaikkien takaisinsoittojen ensimmäisen argumentin odotetaan olevan virhe, jos sellainen tapahtuu .
Virhe # 7: Oletetaan, että luku on kokonaislukutietotyyppi
JavaScriptin numerot ovat liukulukuja - kokonaislukutietotyyppiä ei ole. Et voi odottaa tämän olevan ongelma, koska riittävän suuria lukuja, jotka korostavat kelluntarajoja, ei kohdata usein. Juuri silloin tapahtuu tähän liittyviä virheitä. Koska liukulukuihin voi sisältyä vain kokonaisluvut tiettyyn arvoon saakka, arvon ylittäminen missä tahansa laskennassa alkaa heti sekoittaa sitä. Niin outoa kuin se saattaa tuntua, seuraava arvioi tosi Node.js: ssä:
Math.pow(2, 53)+1 === Math.pow(2, 53)
Valitettavasti oudot numerot JavaScriptissä eivät pääty tähän. Vaikka numerot ovat liukulukuja, myös kokonaislukutietotyyppejä käyttävät operaattorit toimivat täällä:
5 % 2 === 1 // true 5 >> 1 === 2 // true
Toisin kuin aritmeettiset operaattorit, bittioperaattorit ja siirtooperaattorit työskentelevät kuitenkin vain tällaisten suurten 'kokonaisluku' -numeroiden 32 bitin lopussa. Esimerkiksi yrittää siirtää 'Math.pow (2, 53)' yhdellä arvioidaan aina arvoon 0. Yritetään tehdä bittiä tai 1 samalla suurella määrällä arvioidaan arvoon 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
Saatat joutua harvoin käsittelemään suuria lukuja, mutta jos teet niin, on paljon isoja kokonaislukukirjastoja, jotka toteuttavat tärkeät matemaattiset operaatiot suurilla tarkkuusluvuilla, kuten solmu-bigint .
Virhe # 8: Streaming-sovellusliittymien etujen huomiotta jättäminen
Oletetaan, että haluamme rakentaa pienen välityspalvelimen kaltaisen verkkopalvelimen, joka palvelee vastauksia pyyntöihin hakemalla sisältöä toisesta verkkopalvelimesta. Rakennamme esimerkiksi pienen verkkopalvelimen, joka palvelee Gravatar-kuvia:
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.gravatar.com/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)
Tässä nimenomaisessa Node.js-ongelman esimerkissä haemme kuvaa Gravatarista, luemme sen puskuriksi ja vastaamme sitten pyyntöön. Tämä ei ole niin huono asia, koska Gravatar-kuvat eivät ole liian suuria. Kuvittele kuitenkin, olisiko välityspalvelimemme koko kooltaan tuhansia megatavuja. Paljon parempi lähestymistapa olisi ollut tämä:
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.gravatar.com/avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) { resp.pipe(res) }) }) .listen(8080)
Täältä haemme kuvan ja lähetämme vastauksen asiakkaalle. Missään vaiheessa meidän ei tarvitse lukea koko sisältöä puskuriin ennen sen tarjoamista.
Virhe # 9: Console.login käyttäminen virheenkorjaustarkoituksiin
Node.js: ssä “console.log” antaa sinun tulostaa melkein mitä tahansa konsolille. Välitä objekti sille ja se tulostaa sen JavaScript-objektin kirjaimena. Se hyväksyy minkä tahansa mielivaltaisen määrän argumentteja ja tulostaa ne kaikki siististi välilyönneillä. On useita syitä, miksi kehittäjä saattaa tuntea houkutusta käyttää tätä koodinsa virheenkorjaukseen; On kuitenkin erittäin suositeltavaa välttää ”console.log” oikeassa koodissa. Sinun tulisi välttää kirjoittamasta ”console.log” koko koodiin virheenkorjaamiseksi ja sitten kommentoimaan niitä, kun niitä ei enää tarvita. Käytä sen sijaan yhtä upeista kirjastoista, jotka on rakennettu juuri tätä varten, kuten virheenkorjaus .
Tällaiset paketit tarjoavat käteviä tapoja ottaa tietyt virheenkorjauslinjat käyttöön ja poistaa ne käytöstä, kun käynnistät sovelluksen. Esimerkiksi virheenkorjauksen avulla voidaan estää virheenkorjauslinjojen tulostuminen päätelaitteelle asettamatta ympäristömuuttujaa DEBUG. Sen käyttö on yksinkertaista:
// app.js var debug = require('debug')('app') debug('Hello, %s!', 'world')
Voit ottaa virheenkorjausrivit käyttöön suorittamalla tämän koodin siten, että ympäristömuuttuja DEBUG on asetettu sovellukseen tai *:
DEBUG=app node app.js
Virhe # 10: Ohjaajaohjelmien käyttämättä jättäminen
Riippumatta siitä, onko Node.js-koodisi käynnissä tuotannossa vai paikallisessa kehitysympäristössä, valvojaohjelman monitori, joka voi ohjata ohjelmaa, on erittäin hyödyllinen asia. Yksi käytäntö, jonka kehittäjät suosittelevat usein nykyaikaisten sovellusten suunnittelussa ja toteuttamisessa, suosittelee, että koodisi epäonnistuu nopeasti. Jos tapahtuu odottamaton virhe, älä yritä käsitellä sitä, vaan anna ohjelman kaatua ja pyydä valvojaa käynnistämään se muutamassa sekunnissa. Valvojaohjelmien edut eivät rajoitu vain kaatuneiden ohjelmien uudelleenkäynnistämiseen. Näiden työkalujen avulla voit käynnistää ohjelman uudelleen kaatumisen yhteydessä ja käynnistää ne uudelleen, kun jotkut tiedostot muuttuvat. Tämä tekee Node.js-ohjelmien kehittämisestä paljon miellyttävämmän kokemuksen.
Node.js: lle on saatavana lukuisia valvojaohjelmia. Esimerkiksi:
Kaikilla näillä työkaluilla on hyvät ja huonot puolensa. Jotkut niistä ovat hyviä käsittelemään useita sovelluksia samalla koneella, kun taas toiset pystyvät paremmin hallitsemaan lokia. Jos kuitenkin haluat aloittaa tällaisen ohjelman, kaikki nämä ovat oikeudenmukaisia valintoja.
Johtopäätös
Kuten voit kertoa, joillakin näistä Node.js-ongelmista voi olla tuhoisia vaikutuksia ohjelmallesi. Jotkut saattavat aiheuttaa turhautumista, kun yrität toteuttaa yksinkertaisia asioita Node.js: ssä. Vaikka Node.js on tehnyt tulokkaiden aloittamisen erittäin helpoksi, siinä on silti alueita, joihin on yhtä helppo sekoittaa. Muiden ohjelmointikielien kehittäjät voivat pystyä liittymään joihinkin näistä ongelmista, mutta nämä virheet ovat melko yleisiä uusien joukossa Node.js-kehittäjät . Onneksi niitä on helppo välttää. Toivon, että tämä lyhyt opas auttaa aloittelijoita kirjoittamaan paremman koodin Node.js-tiedostoon ja kehittämään vakaan ja tehokkaan ohjelmiston meille kaikille.