node-js备忘(三)

一. 回调地狱 callbackhell

image

异步操作是没有先后顺序的

例如:

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

此时,我们想到,用回调的方式保证顺序

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

  1. 创建Promise容器
image

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-黑马程序员_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili​www.bilibili.com

图标

例2 :jquery封装好的promise方法

image

所以说,如果一个业务需要3个以上的接口,则应该用这种方式比较好

例3: 将get请求改造为可以promise的方式

image

三. mongoose所有API都支持promise

image

四 path核心模块

1.引用path

var path=require('path')
  1. path.basename('路径名',['后缀名'])
path.basename('C:/a/b/c/index.html')//获取一个路径的文件名部分 path.basename('C:/a/b/c/index.html','html')//获取一个路径的文件名部分(除去后缀)
  1. 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')
image

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,里面写了一个非常酷炫的页面头

我们要在其他好几个页面里引用它

image

则,我们在模板里直接写:{{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>
image

2.模板继承

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

image

这里我们可以设计一个模板页面,并挖一个坑. 使用 {{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}}
image

六. MD5校验

这里我们用一个第三方包blueimp-md5

  1. 安装
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-session​www.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);
    })
})
image
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容