一. 回调地狱 callbackhell

异步操作是没有先后顺序的
例如:
var fs = require("fs")
fs.readFile('./data/a.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
})
fs.readFile('./data/b.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
})
fs.readFile('./data/c.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
})

此时,我们想到,用回调的方式保证顺序
var fs = require("fs")
fs.readFile('./data/a.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
fs.readFile('./data/b.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
fs.readFile('./data/c.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
})
})
})
如果有很多层怎么办??
那就想一个地狱,一层套一层 这让我想起了flutter😋
二. Promise
只要出现异步嵌套,就可以使用promise方式解决异步嵌套问题~ 好多回调其实都可以写成promise.then的方式
为解决回调地狱问题, ES6新增了一个API: Promise
- 创建Promise容器

Promise容器创建后有一个承诺状态pending(等待)
如果内部异步任务执行成功,应该把状态改为resolved(解决),失败则改为rejected(失败)
new Promise(
里面放一个异步任务作为承诺 //
如果异步任务执行
)
值得注意的是,承诺一旦创建就开始执行
例如:
var p1=new Promise(
function (resolve,reject) {
fs.readFile('./data/a.txt', 'utf8', (err, data) => {
if (err) reject(err);//把容器的pending状态改为rejected
resolve(data);//把容器的pending状态改为resolved
});
}
)
2.承诺完成后执行 p1.then(成功回调,失败回调)
var fs = require("fs")
var p1=new Promise(
function (resolve,reject) {
fs.readFile('./data/a.txt', 'utf8', (err, data) => {
if (err) reject(err);//把容器的pending状态改为rejected
resolve(data);//把容器的pending状态改为resolved
});
}
)
p1.then(
(data) => {
console.log(data);//这里的data是resolve(data)传出的data
//也就是说,这个函数传入上面的resolve(data)
},
(err) => {
console.log(err);//这里的data是reject(err)传出的err
//也就是说,这个函数传入上面的reject(err)
})
3.解决上面的回调地狱
var fs = require("fs")
var p1 = new Promise(
function (resolve, reject) {
fs.readFile('./data/a.txt', 'utf8', (err, data) => {
if (err) reject(err);//把容器的pending状态改为rejected
resolve(data);//把容器的pending状态改为resolved
});
}
)
var p2 = new Promise(
function (resolve, reject) {
fs.readFile('./data/b.txt', 'utf8', (err, data) => {
if (err) reject(err);//把容器的pending状态改为rejected
resolve(data);//把容器的pending状态改为resolved
});
}
)
var p3 = new Promise(
function (resolve, reject) {
fs.readFile('./data/c.txt', 'utf8', (err, data) => {
if (err) reject(err);//把容器的pending状态改为rejected
resolve(data);//把容器的pending状态改为resolved
});
}
)
p1.then(
(data) => {
console.log(data);
return p2
},
(err) => {
console.log(err);
}).then((data) => {
console.log(data);
return p3
},
(err) => {
console.log(err);
}).then((data) => {
console.log(data);
},
(err) => {
console.log(err);
})
此方法可以称为:异步调用链式编程
无非是另一个稍微好看的地狱而已
我们在进一步封装一下
var fs = require("fs")
function pReadFile(filePath) {
return new Promise(
function (resolve, reject) {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) reject(err);//把容器的pending状态改为rejected
resolve(data);//把容器的pending状态改为resolved
});
}
)
}
pReadFile('./data/a.txt')
.then(
(data) => {
console.log(data);
return pReadFile('./data/b.txt')
},
(err) => {
console.log(err);
})
.then(
(data) => {
console.log(data);
return pReadFile('./data/c.txt')
},
(err) => {
console.log(err);
})
.then(
(data) => {
console.log(data);
},
(err) => {
console.log(err);
})
关于promise的用法举例
如果我们渲染一张页面需要访问两个数据表,两个数据都拿到才可以渲染,那么可以使用这种方法.
Node.JS-黑马程序员_哔哩哔哩 (゜-゜)つロ 干杯~-bilibiliwww.bilibili.com

例2 :jquery封装好的promise方法

所以说,如果一个业务需要3个以上的接口,则应该用这种方式比较好
例3: 将get请求改造为可以promise的方式

三. mongoose所有API都支持promise

四 path核心模块
1.引用path
var path=require('path')
- path.basename('路径名',['后缀名'])
path.basename('C:/a/b/c/index.html')//获取一个路径的文件名部分 path.basename('C:/a/b/c/index.html','html')//获取一个路径的文件名部分(除去后缀)
- path.dirname('路径名') //获取路径
path.dirname('C:/a/b/c/index.html')
4. path.extname('路径名') //获取扩展名
path.extname('C:/a/b/c/index.html')
5.path.isAbsolute('路径名') //是否是绝对路径
path.isAbsolute('C:/a/b/c/index.html')
6.path.parse('路径名') //将路径转化为一个对象
path.parse('C:/a/b/c/index.html')

7. path.join(路径1, 增加路径2') //路径拼接
path.join('C:/a/','b')
把相对路径转为绝对路径
path.join(__dirname,'./index.html')

8. _ _dirname 和 _ _filename
在每个模块中,除了require和exports等模块相关API外,还有两个特殊成员
- __dirname :可以用来动态获取当前文件的所属目录的绝对路径
- __filename :可以用来动态获取当前文件的绝对路径
例如:


注意,文件操作路径中,相对路径设计为:相对于''执行node 命令的地方''的路径.
所以说,如果 a/b/c文件这样的文件结构中 这里注意c是文件,不是模块
a下执行一个脚本,调用b里面的模块,b模块里调用c文件,c文件里有相对路径的话,系统会以为是a/c这个路径,从而导致错误.所以在文件操作中,使用相对路径是不可靠的
这时我们就需要将相对路径改为绝对路径
path.join(__dirname,'./index.html')
注意: 引用模块时候的路径标识并不受影响, 文件中的相对路径才会受影响
五. art-template的include-extend
在node中,有很多第三方模板引擎,不仅仅是art-template
还有:ejs jade(pug) handlebars nunjucks 等
1.include
例如:我们的有一个网页header.html,里面写了一个非常酷炫的页面头
我们要在其他好几个页面里引用它

则,我们在模板里直接写:{{include './header.html'}}就可以引用了!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{{include './header.html'}}
<h1>hello {{name}}!!!</h1>
</body>
</html>

2.模板继承
每个html的开头和结尾都一样,只是内容不一样,我们是否可以设置一个模板呢?

这里我们可以设计一个模板页面,并挖一个坑. 使用 {{block '坑名'}} 默认内容 {{/block}
比如:我们设计一个html模板 layout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.css">
{{block 'head'}} {{/block}}
</head>
<body>
{{include './header.html'}}
<!-- 挖坑 -->
{{block 'content'}}
<h1>默认内容</h1>
{{/block}}
{{include './footer.html'}}
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script src="/node_modules/bootstrap/dist/js/bootstrap.js"></script>
{{block 'script'}} {{/block}}
</body>
</html>
然后,我们的index.html可以套用这个模板
{{extend './layout.html'}}
<!-- 引入默认内容 -->
{{block 'head'}}
<style> body{
background-color: cadetblue;
}</style>
{{/block}}
{{block 'content'}}
<h1>index.html填入的新内容~</h1>
{{/block}}
{{block 'script'}}
<script>window.alert("index弹出页面")</script>
{{/block}}

六. MD5校验
这里我们用一个第三方包blueimp-md5
- 安装
cnpm install blueimp-md5 --save
2. 引包
var md5 = require('blueimp-md5')
3.使用
//对密码进行md5加密 req.body.password=md5(md5(req.body.password))
//对密码进行解密 md5(md5(req.body.password))
七. 表单异步提交处理
关于同步和异步处理的区别,请参看:
https://www.bilibili.com/video/av27670326?p=107www.bilibili.com
表单具有默认提交行为,默认是同步的(get. post都是),此时,浏览器会锁死(转圈儿)等待服务器的响应. 服务器响应后,浏览器把收到的结果覆盖原页面
前端Ajax进行异步请求,期望服务器给它回复一个类型的数据,比如json类数据
$.ajax({
url: '/login',
type: 'post',
data: formData,
dataType: 'json',
success: function (data) {
var err_code = data.err_code
if (err_code === 0) {
// window.alert('注册成功!')
// 服务端重定向针对异步请求无效
window.location.href = '/' //重定向
} else if (err_code === 1) {
window.alert('邮箱或者密码错误')
} else if (err_code === 500) {
window.alert('服务器忙,请稍后重试!')
}
}
注意:如果浏览器是异步请求,服务端重定向是不管用的,需要浏览器重定向
这时,我们的服务器应该返回json
router.post('/register', (req, res) => {
User.find({
$or: [
{ email: req.body.email },
{ nickname: req.body.nickname }
]
}, (err, data) => {
console.log(data.length);
if (err) {
console.log('数据库查找失败');
return res.status(500).json({
err_code: 500,
msg: '数据库查找失败'
})
}
if (data != 0) {
console.log('用户已存在');
return res.status(200).json({
err_code: 1,
msg: '用户已存在'
})
}
//对密码进行md5加密
req.body.password = md5(md5(req.body.password))
new User(req.body).save((err, ret) => {
if (err) return res.status(500).json({
err_code: 500,
msg: '数据库存储错误'
})
//注册成功,
return res.json({
err_code: 0,
msg: '用户创建成功'
})
})
})
})
八. session记录用户状态
express默认没有session的,需要第三方包:
express-sessionwww.npmjs.com!
1.安装
cnpm install express-session --save
2.引包
var session=require('express-session')
3.配置
app.use(session({
secret: 'keyboard cat',//加密字符串
resave: false,
saveUninitialized: true //无论你是不是使用session,系统都会默认给你一把钥匙
}))
4.使用
配置好之后就可以用req.session来访问和设置Session成员了
req.session是一个对象,可以用操作对象的方式操作它
添加数据: req.session.foo='asdas'
访问数据: req.session.foo
删除数据: req.session.foo=null
例子:用户登录后,我们读取用户信息,生成一个user对象,然后存入session中
req.session.user=user
这就算登录成功了,服务器重定向到某个页面,我们此时可以以登录状态渲染
router.get('/', (req, res) => { res.render('./index.html', { user: req.session.user }) })
5.持久化session
默认session是内存存储的,服务器一重启会丢失,真正的生产环境session会持久化存储.
一般使用插件自动存到数据库中
九. 中间件
什么是中间件: 就是处理请求的,本质是个函数
请求进来不是直接处理,而是把req res传给封装好的方法处理,处理过后,请求有了更强大的属性,比如req原先没有body,传递给XXX中间件后,它就有了body.
中间件本身就是一个方法,他有四个形参:
err 错误对象(只有4个参数写全的时候,把他放在第一个,才会正确解析)
req 请求对象
res 响应对象
next 下一个中间件 (没有next是不会执行下一个中间件的,下一个中间件就是程序中写在它下面的第一个匹配的中间件,如果没有能匹配的中间件,则会输出 Cannot GET /POST .....)
1.不关心请求路径和方法的中间件
不管什么请求,不管请求方法和路径,都会经过这个中间件
app.use((req,res,next)=>{
console.log('请求进来了! --1');
next()
})
app.use((req,res,next)=>{
console.log('请求进来了! --2');
next()
})
2. 关心请求路径的中间件
app.use('/a',(req,res,next)=>{
console.log('请求进来了! --a');
next()
})
只有 /a开头的请求才会进入
3. 严格关心请求路径和请求方法的中间件
其实就是我们最常用的
app.get('/',()=>{})
app.post('/',()=>{})
4. body-parser也是个中间件
5.配置一个404错误处理中间件
app.use((req,res)=>{ res.render('404.html') })
6.配置一个全局错误处理中间件
如果next()传入参数,会直接执行带有4个参数的中间件, 因此,我们可以用它做一个全局的错误处理中间件
写完这个中间件后,err都不用单独处理了~o( ̄▽ ̄)d
app.use((err,req,res,next)=>{ res.status(500).send(err) })
这样我们在处理错误时,直接next(err)就可以了,比如
app.get('/a',(req,res,next)=>{
fs.readFile('./asdas.aa',(err,data)=>{
if (err) {
console.log(err);
next(err)
};
console.log(data);
})
})
