来自 前端知识 2019-12-07 15:32 的文章
当前位置: 金沙澳门官网网址 > 前端知识 > 正文

Node技术实现的多用户博客系统教程,开发者的

NodeJS 开发者的 10 个常见错误

2015/06/16 · CSS · 1 评论

本文由 伯乐在线 - 刘健超-J.c 翻译,黄利民 校稿。未经许可,禁止转载!
英文出处:www.toptal.com。欢迎加入翻译组。

自 Node.js 公诸于世的那一刻,就伴随着赞扬和批评的声音。这个争论仍在持续,而且并不会很快消失。而我们常常忽略掉这些争论产生的原因,每种编程语言和平台都是因某些问题而受到批评,而这些问题的产生,是取决于我们如何使用这个平台。不管有多难才能写出安全的 Node.js 代码,或有多容易写出高并发的代码,该平台已经有相当长一段时间,并已被用来建立一个数量庞大、稳健和成熟的 web 服务器。这些 web 服务器伸缩性强,并且它们通过在 Internet 上稳定的运行时间,证明自己的稳定性。

然而,像其它平台一样,Node.js 容易因开发者问题而受到批评。一些错误会降低性能,而其它一些问题会让 Node.js 直接崩溃。在这篇文章里,我们将会聊一聊关于 Node.js 新手的 10 个常犯错误,并让他们知道如何避免这些错误,从而成为一名 Node.js 高手。

图片 1

本章主要讲什么(一句话)?

本章主要讲解:利用mongoose第三方库进行业务数据操作

图片 2

                                                  --  

错误 #1:阻塞事件循环

JavaScript 在 Node.js (就像在浏览器一样) 提供单线程执行环境。这意味着你的程序不能同时执行两部分代码,但能通过 I/O 绑定异步回调函数实现并发。例如:一个来自Node.js 的请求是到数据库引擎获取一些文档,在这同时允许 Node.js 专注于应用程序其它部分:

JavaScript

// 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.. // 尝试从数据库中获取一个用户对象。在这个函数执行的一刻,Node.js 有空去运行代码其它部分.. db.User.get(userId, function(err, user) { // .. until the moment the user object has been retrieved here // .. 直到用户对象检索到这里的那一刻 })

1
2
3
4
5
6
// 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..
// 尝试从数据库中获取一个用户对象。在这个函数执行的一刻,Node.js 有空去运行代码其它部分..
db.User.get(userId, function(err, user) {
// .. until the moment the user object has been retrieved here
        // .. 直到用户对象检索到这里的那一刻
})

图片 3

然而,具有计算密集型代码的 Node.js 实例被数以万计客户端同时连接执行时,会导致阻塞事件循环,并使所有客户端处于等待响应状态。计算密集型代码,包括尝试给一个庞大数组进行排序操作和运行一个格外长的循环等。例如:

JavaScript

function sortUsersByAge(users) { users.sort(function(a, b) { return a.age > b.age ? -1 : 1 }) }

1
2
3
4
5
function sortUsersByAge(users) {
users.sort(function(a, b) {
return a.age > b.age ? -1 : 1
})
}

基于小 “users” 数组执行 “sortUserByAge” 函数,可能没什么问题,当基于庞大数组时,会严重影响整体性能。如果在不得不这样操作的情况下,你必须确保程序除了等待事件循环而别无他事(例如,用 Node.js 建立命令行工具的一部分,整个东西同步运行是没问题的),然后这可能没问题。然而,在 Node.js 服务器实例尝试同时服务成千上万个用户的情况下,这将是一个毁灭性的问题。

如果用户数组是从数据库检索出来的,有个解决办法是,先在数据库中排序,然后再直接检索。如果因需要计算庞大的金融交易历史数据总和,而造成阻塞事件循环,这可以创建额外的worker / queue 来避免阻塞事件循环。

正如你所看到的,这没有新技术来解决这类 Node.js 问题,而每种情况都需要单独处理。而基本解决思路是:不要让 Node.js 实例的主线程执行 CPU 密集型工作 – 客户端同时链接时。

一、前言

上一章主要对项目引入MongoDB进行数据存储,并导入mongoose第三方组件,完成mongodb数据库配置及连结代码,本节继续。

错误 #2:调用回调函数多于一次

JavaScript 一直都是依赖于回调函数。在浏览器中,处理事件是通过调用函数(通常是匿名的),这个动作如同回调函数。Node.js 在引进 promises 之前,回调函数是异步元素用来互相连接对方的唯一方式 。现在回调函数仍被使用,并且包开发者仍然围绕着回调函数设计 APIs。一个关于使用回调函数的常见 Node.js 问题是:不止一次调用。通常情况下,一个包提供一个函数去异步处理一些东西,设计出来是期待有一个函数作为最后一个参数,当异步任务完成时就会被调用:

JavaScript

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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),而最后一个 “done” 则可省略返回语句。这是因为调用回调函数后,并不会自动结束当前执行函数。如果第一个 “return” 注释掉,然后给这个函数传进一个非字符串密码,导致 “computeHash” 仍然会被调用。这取决于 “computeHash” 如何处理这样一种情况,“done” 可能会调用多次。任何一个人在别处使用这个函数可能会变得措手不及,因为它们传进的该回调函数被多次调用。

只要小心就可以避免这个 Node.js 错误。而一些 Node.js 开发者养成一个习惯是:在每个回调函数调用前添加一个 return 关键字。

JavaScript

if(err) { return done(err) }

1
2
3
if(err) {
return done(err)
}

对于许多异步函数,它的返回值几乎是无意义的,所以该方法能让你很好地避免这个问题。

二、技术关健词

Node、MongoDB、Angular2、mongoose

错误 #3:函数嵌套过深

函数嵌套过深,时常被称为“回调函数地狱”,但这并不是 Node.js 自身问题。然而,这会导致一个问题:代码很快失去控制。

JavaScript

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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’)
})
})
})
}

图片 4

任务有多复杂,代码就有多糟糕。以这种方式嵌套回调函数,我们很容易就会碰到问题而崩溃,并且难以阅读和维护代码。一种替代方式是以函数声明这些任务,然后将它们连接起来。尽管,有一种最干净的方法之一 (有争议的)是使用 Node.js 工具包,它专门处理异步 JavaScript 模式,例如 Async.js :

JavaScript

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() { // ... }) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 提供了很多其它函数来解决不同的异步模式。为了简洁,我们在这里使用一个较为简单的案例,但实际情况往往更糟。

三、本章涉及核心技术点

图片 5

错误 #4:期望回调函数以同步方式运行

异步程序的回调函数并不是 JavaScript 和 Node.js 独有的,但它们是造成回调函数流行的原因。而对于其它编程语言,我们潜意识地认为执行顺序是一步接一步的,如两个语句将会执行完第一句再执行第二句,除非这两个语句间有一个明确的跳转语句。尽管那样,它们经常局限于条件语句、循环语句和函数调用。

然而,在 JavaScript 中,回调某个特定函数可能并不会立刻运行,而是等到任务完成后才运行。下面例子就是直到没有任何任务,当前函数才运行:

JavaScript

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

1
2
3
4
5
6
7
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 将每个文件视为一个孤立的小模块。如果你的包(package)含有两个文件,或许是 “a.js” 和 “b.js”。因为 “b.js” 要获取 “a.js” 的功能,所以 “a.js” 必须通过为 exports 对象添加属性来导出它。

JavaScript

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

1
2
// a.js
exports.verifyPassword = function(user, password, done) { ... }

当这样操作后,任何引入 “a.js” 模块的文件将会得到一个带有属性方法 “verifyPassword” 的对象:

JavaScript

// b.js require(‘a.js’) // { verifyPassword: function(user, password, done) { ... } }

1
2
// b.js
require(‘a.js’) // { verifyPassword: function(user, password, done) { ... } }

然而,如果我们想直接导出这个函数,而不是作为某个对象的属性呢?我们能通过覆盖 exports 对象来达到这个目的,但我们不能将它视为一个全局变量:

JavaScript

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

1
2
// a.js
module.exports = function(user, password, done) { ... }

注意,我们是如何将 “exports” 作为 module 对象的一个属性。在这里知道 “module.exports” 和 “exports” 之间区别是非常重要的,并且这经常会导致 Node.js 开发新手们产生挫败感。

4.1、新建users.js组件来封装【用户】模块功能

废话不多说,先代码奉上:

书接上文,直接在models文件下新建一users.js文件,代码如下:


var db = require('./mongodb');

function User(user) {

this.name = user.name;

this.password = user.password;

this.email = user.email;

};

module.exports = User;

//存储用户信息

User.prototype.save = function(callback) {

//要存入数据库的用户文档

var user = {

name: this.name,

password: this.password,

email: this.email

};

//添加操作

var userModel = new db.Users(user);

userModel.save(function(err,user){

if(err){

console.err(err);

return callback(err);//错误,返回 err 信息

}

console.log("sucess:"+user[0]);

callback(null, user[0]);//成功!err 为 null,并返回存储后的用户文档

});

};

//读取用户信息

User.get = function(name, callback) {

if (name){

db.Users.findOne({"name":name},

function(err,user){

if(err) {

console.err(err);

return callback(err);//失败!返回 err 信息

}

callback(null, user);//成功!返回查询的用户信息

});

}else{

db.Users.find(null,

function(err,users){

if(err) {

console.err(err);

return callback(err);//失败!返回 err 信息

}

callback(null, users);//成功!返回查询的用户信息

});

}

};


图片 6

  1. var db = require('./mongodb');

引入上节课创建的mongodb.js文件,将来会使用其导出的类实例。

  1. function User(user) {

this.name = user.name;

this.password = user.password;

this.email = user.email;

};

module.exports = User;

上述代码,利用函数的方式定义了一User类,三个字段:name,password,email,并通module.exports的方式对外导出。

  1. User.prototype.save = function(callback) {

.....

}

上述代码,动态的为User类定义了一静态属性,该属性实质是一个方法(function),该方法的作用是将来用于保存用户数据,注册用户时会用到,调用该方法成功后,会向Mongodb数据库的users文档中,插入一条记录,传入的参数:为一回调函数,用于执行成功或失败后的操作。

  1. var userModel = new db.Users(user);

userModel.save(function(err,user){

......

})

上述代码,首先创建mongodb.js文件中导出来Users模型实例,然后,核心的真正调用了mongodb数据库引擎向后台插入数据的代码是:userModel.save(function(err,user){......)
其中:save的两个参数,第一个参数err为如果插入出错,接收服务器返回的错误对象,第二个参数user为如果插入成功,返回操作成功的user用户实例集合。

5. 需要注意的是:如果插入成功,返回的user实际是一个集合,这里由于是插入操作,这个集合里将只有一条记录,即新增的user实例,所以接收时,需要使用user[0]的方式,即:
callback(null, user[0]);//成功!err 为 null,并返回存储后的用户文档

6.对这个User的save方法的调用代码,将来将类似于以下这样:

var newUser = new User({

name: name,  //接收前台传递过来的用户名

password: password, //接收前台传递过来的密码

email: req.body.email  //接收前台传递过来的Email

});

......

newUser.save(function(err, user) {   //调用该User实例的save方法

if (err) {

res.send({ status: 'error', message: "出错了,原因如下:" + err });

return;

}

res.send({ status: 'success', message: "注册成功!", user: user });

});

错误 #6:在回调函数内抛出错误

JavaScript 有个“异常”概念。异常处理与大多数传统语言的语法类似,例如 Java 和 C++,JavaScript 能在 try-catch 块内 “抛出(throw)” 和 捕捉(catch)异常:

JavaScript

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

1
2
3
4
5
6
7
8
9
10
11
12
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 放在异步函数内,它会出乎你意料,它并不会执行。例如,如果你想保护一段含有很多异步活动的代码,而且这段代码包含在一个 try-catch 块内,而结果是:它不一定会运行。

JavaScript

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

1
2
3
4
5
6
7
8
9
10
11
12
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, …)模式,所有回调函数的第一个参数期待是一个错误对象。

  1. User.get = function(name, callback) {......})

上述代码,动态的为User类定义了一静态属性get,该属性实质是一个方法(function),该方法的作用是用于通过用户名获取用户信息.两个参数,第一个参数name:用户名,第二个参数callback:查询成功或失败后的回调函数。

  1. 用户查找的核心数据库操作代码是:

db.Users.findOne({"name":name},function(err,user){......})
db.Users.find(null,function(err,users){......})
注意findOne与find的区别,一个是查找一个单一用户(不管查到有多少条记录,均只返回第一条记录),所以回调里返回值是user,而 find查找找返回所有符合条件的,所以是个集合users

  1. 将来对User.get方法的调用的客户端代码,将类似于:

//检查用户名是否已经存在

User.get(newUser.name, function(err, user) {

if (err) {

res.send({ status: 'error', message: "出错了,原因如下:" + err });

return;

}

if (user) {

res.send({ status: 'failed', message: "用户已存在!" });

return;

})

本文由金沙澳门官网网址发布于前端知识,转载请注明出处:Node技术实现的多用户博客系统教程,开发者的

关键词: