Nodejs基础2
Asynchronous JavaScript
Synchronous vs Asynchronous Code
在现实中我们更多的是使用数据库而非数组。在我们讨论Node链接mongodb之前我们先要了解一下异步编程
首先新建一个async文件夹
1 | console.log("Before"); |
这是一个同步编程(阻塞机制) 的演示。在这种机制之下,第一行执行的时候,程序会暂停,第二行需要第一行结束之后才会执行
相反的,我们有异步编程
1 | console.log("Before"); |
比如说这个函数,我们用setTimeout函数来模仿从数据库中读取数据需要2秒种的时间。那么这时候还是先Before再等两秒输出”Reading a user from a database….”,最后After吗?不是的,打印结果如下
Before
After
Reading a user from a database….
我们立即打印了Before和after,但是等了两秒之后才显示了读取的信息
这就是异步机制。异步机制不是多线程,在这个例子中只有一个线程。
相当于饭店中的服务员不会只服务你一个人,在厨师准备你的餐点的时候,他会取招待其他的顾客。
Patterns for Dealing with Asynchronous Code
当我们标记一个异步函数的时候不能这样写
1 | console.log("Before"); |
这样写 我们会得到user是undefined的
Callbacks
我们可以给getUser传入一callback参数,它是再异步操作完毕后调用的参数
1 | console.log("Before"); |
Before
After
Reading a user from a database….
User { id: 1, gitHubname: ‘Jason’ }
当一个异步操作的结果 is ready,callback函数就会被调用并传入下面返回结果
1 | console.log("Before"); |
Before
After
//两秒之后
Reading a user from a database….
//两秒之后
Calling GitHub Api…
Repos [ ‘repo1’, ‘repo2’, ‘repo3’ ]
Callback Hell
我们真实的代码可能远远比这个复杂。
1 | console.log("Before"); |
如果所有的异步代码都这样那还得了?全是很深的嵌套解构
我们把它叫做回调陷阱/回调地狱
Named Functions to Rescue
我们用 这种方式,用引用的方式调用函数。
调用getRepositories ,那么就再getRespositories中调用getCommits
1 | console.log("Before"); |
Promises
promise就是一个用于保存异步操作结果的容器。当一个异步操作结束后,要么保存了值,要么保存了错误信息。promise就保证给你一个异步操作的结果。这个对象有三个states
1.当我创建了promise对象的时候,它处于Pending阶段。
2.当结果准备好,promise将被履行,也就是说异步操作成功完成,这样就得到一个值。进入Fulfiled阶段
3.否则如果在执行异步操作的过程当中出现了问题,promise进入Reject阶段
我们用下面这个例子来模拟异步操作成功了. resolve就代表了如果异步操作成功返回的值
然后p.then 就代表了成功以后进行的操作。运行以后我们看到两秒钟之后打印了Result 1
1 | const p = new Promise((resolve, reject) => { |
下面这个例子模仿了失败的异步操作,如果有错误,我们需要 p.catch来捕获这个错误,然后进行错误之后的动作
两秒之后我们得到了 Error message的打印信息
1 | const p = new Promise((resolve, reject) => { |
Replacing Callbacks with Promises
现在我们用promise来取代callbacks
1 | //... |
Consuming Promises
我们可以链式处理复杂的异步请求,也就是完成了第一步,then第二步,then第三步
但是不论何时使用Promise都需要我们捕捉错误
1 | console.log('Before'); |
Creating Settled Promises
有时候需要创建一个already resolved 的 promise 这在单元测试中比较常见
现在我们模仿一个请求服务器的异步请求成功完成,在单元测试中我们需要创建一个已经实现的Promise
1 | const p = Promise.resolve({id:1}); |
有时候我们需要创建一个 already reject的promise,那么我们需要返回一个Error对象,因为里面包含了堆栈错误信息。
1 | const p = Promise.reject(new Error('reason for rejection...')); |
Running Promises in Parallel
all
我这里有俩Promise对象,现在我想同时操作这两个Promises,当他们都做完了以后我们再去做我们想要的东西。所以我们调用Promise.all传入一个Promise数组。这里将返回一个新的Promise,他将在所有Promise对象全部resolve了以后才resolve
1 | const p1 = new Promise((resolve) => { |
Async operation 1..
Async operation 2..
[ 1, 2 ]
2s之后打印出这样的信息
注意,这个不是并发操作,这仍然是单线程,单线程几乎是同时进行两个 异步操作的。但这并不是真的同时。
当我们其中一个异步操作产生结果的时候,会把resolve的值存放在数组当中
注意,我们这里没有些error,但是如果Promise.all捕捉到了一个error的话(只要其中一个Promise出错了),全部Promise就没有最终返回值了
race
但是有时候,我们并不想让这些Promises全部都满足才进行下一步操作,我们可以让一个promise完成后就进行下一步操作。这时候我们需要用到race
这时候 Promise数组中的一个promise is resolved 整个Promise.race就被认为是resolved 了.这时候,返回的结果不再是一个数组了,而是最快实现Promise的旅行值
1 | Promise.race([p1,p2]).then((result) => console.log(result)); |
Async and Await
之前我们学习了用callback函数来处理异步请求。然后用Promise来重写了。但是我们可以用js的新特性。
async和await.它可以让我们像写同步操作一样写异步操作
但是我们还是需要有一个捕获错误的功能,这时候就需要 try和catch了
1 | console.log("Before"); |
Before
after
Reading a user from a database….
Calling GitHub Api…
Calling GitHub Api…
[ ‘commit’ ]
Exercise
用async和await改造这段代码
1 |
|
答:
1 | async function notifyCustomer() { |
Customer { id: 1, name: 'Mosh Hamedani', isGold: true, email: 'email' }
Top movies: Promise { <pending> }
Email sent...
CRUD Operations Using Mongoose
express 可以使用很多的数据库,这里我们用MongoDB。而且在node和express中经常使用
Connecting to MongoDB
1 | const mongoose = require("mongoose"); |
Connected to MongoDB
Schemas
这里 collection就是一张表,document就是列。信息是由键值对保存的
我们用schema来设计符合MongoDB集合的文档结构
我们新建张表格,在创建的时候我们创建一个对象,然后传入我们需要传入文档的键值对
这里又name:string类型,auther:string类型 ,tags:string 类型的数组(保存后是一个键值对数组 0:basic,1:advanced)之类的,date:一个对象,对象类型是日期,默认值是当前日期。 isPublished:布尔类型
Schema Types: String,Number,Date,Buffer(字节格式的数据),Boolean,ObjectID,Array
1 | const mongoose = require("mongoose"); |
Models
我们用courseSchema来定义数据库中course的文档结构,现在我们要把它弄成一个Model(模型)
在这里我们有一个Course,我们就可以创建一个类对象nodeCourse,然后我们就可以把nodeCourse保存到数据库当中。为了创建一个Course,我们可以把courseSchema变成Model
mongoose.model有两个参数,第一个参数是目标集合(单数)名称,也就是数据库中的collection集合;第二个参数是这个集合所需要的schema结构。
1 | const mongoose = require("mongoose"); |
Saving a Document
保存到数据库是一个异步操作。因为保存到数据库得花点时间。因为我们要访问文件系统。
1 | const mongoose = require("mongoose"); |
不像关系型数据库,我们不用创建表格,设计表格我们只要创建文档然后存进去就可以了。
现在我们多建几个信息
Querying Documents
现在我们来查询信息:
model.find()
我可以得到一个文档的列表
model.findById()
根据id找
model.findOne()
返回一个单一的文档
我们也可以 model.find().then()
1 | //... |
这样我们就得到了两门课程
我们也可以加上一个过滤器,比如我们可以这样
1 | async function getCourses() { |
我们也可以
显示信息的数目,在括号中的数值
对数据进行排序,传入一个对象,里面的键值对用来进行排序。升序写1,降序写-1
对返回的列进行选择,传入一个对象 ,想得到name和tags 那么就写1,代表选中
1 | async function getCourses() { |
Comparison Query Operators比较操作符
在mongo中我们有很多操作符用来做值得比较。这些mongodb使用的操作符在mongoose中同样有效。
eq (equal)
ne (not equal)
gt(greater than)
gte (greter than or equal to)
lt (less than)
lte (less than or equal to )
in
nin (not in )
比如我们想找出价格大于10dollar的课程
在Mysql基础当中,我们知道了可以直接用大于小于符号来选择,如果是in的话可以这样写IN (‘VA’,’FL’,’GA’)
我们怎么在json对象中描述呢?
我们用一个对象来代替数字,对象中是一个键值对$ 代表这是一个运算符,我们用gt来表示greater than 冒号后面的是数字。
1 | const courses = await Course |
那么我们如果想找出大于10 小于20 的课程呢?
1 | const courses = await Course |
那么我如果想找10,15 或者20 dollar的课程呢? 我们利用数组
1 | const courses = await Course |
Logical Query Operators逻辑运算符
Course.find({author:'Mosh',isPublished:true});
表示的是and,如果我们想要表示or 有应该怎么写呢?
我们可以先find()也就是全部找到
然后用.and 和 .or 来表达内部的逻辑是and还是or,传入的参数是一个对象数组,里面存放我们需要查找的对象
1 | const courses = await Course |
Regular Expressions
到现在为止我们得到的都是确定的字符串,但是我们想模糊查询,怎么办呢?
比如我想得到以Mosh开头的课程,我们可以这样写
\^ 是表示开头, $表示结尾 。如果不区分大小写
/i (忽略大小写)
/g (全文查找出现的所有匹配字符)
/m (多行查找)
/gi(全文查找、忽略大小写)
/ig(全文查找、忽略大小写)
如果我们只是想找出包含mosh的
1 | const courses = await Course |
Counting
我们如果比较关心数量而不是对象本身,我们可以这样写
1 | const courses = await Course |
Pagination
与limit方法如影随形的是skip方法,常常用在分页器当中使用
为了实现分页,我们需要调过前面一页的所有文档。
1 | async function getCourses(){ |
Exercise 1
导入数据后。我们要达到这个目标
1 | const mongoose = require('mongoose'); |
Exercise 2
1 | const mongoose = require('mongoose'); |
Exercise 3
1 | const mongoose = require('mongoose'); |
Updating a Document- Query First
比如我有个ispublished=true的话,author不允许被修改的规则,那么我们可以这样修改if(course.isPublished) return ;
如何来更新数据库文档?
先来介绍 Query First 也就是先查询再更新
Approach:Update first
findById
Modify its properties
save()
1 | async function updateCourse(id){ |
Updating a Document- Update First
我们也可以直接接入数据库修改文档
Approach:Update first
Update directly
Optionally: get the updated document
1 | async function updateCourse(id){ |
Removing Documents
怎么删除文档呢?
1 | async function removeCourse(id){ |