Nodejs基础2

Nodejs基础2

Nodejs基础1

Nodejs基础2

Nodejs基础3

Nodejs基础4

Asynchronous JavaScript

Synchronous vs Asynchronous Code

在现实中我们更多的是使用数据库而非数组。在我们讨论Node链接mongodb之前我们先要了解一下异步编程

首先新建一个async文件夹

1
2
console.log("Before");
console.log("After");

这是一个同步编程(阻塞机制) 的演示。在这种机制之下,第一行执行的时候,程序会暂停,第二行需要第一行结束之后才会执行

相反的,我们有异步编程

1
2
3
4
5
console.log("Before");
setTimeout(() => {
console.log("Reading a user from a database....");
}, 2000);
console.log("After");

比如说这个函数,我们用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
2
3
4
5
6
7
8
9
10
11
console.log("Before");
const user = getUser(1);
console.log(user)
console.log("After");

function getUser(id) {
setTimeout(() => {
console.log("Reading a user from a database....");
return {id: id,gitHubname:'Jason'}
}, 2000);
}

这样写 我们会得到user是undefined的

Callbacks

我们可以给getUser传入一callback参数,它是再异步操作完毕后调用的参数

1
2
3
4
5
6
7
8
9
10
11
12
console.log("Before");
const user = getUser(1, function (user) {
console.log("User", user);
});
console.log("After");

function getUser(id, callback) {
setTimeout(() => {
console.log("Reading a user from a database....");
callback({ id: id, gitHubname: "Jason" });
}, 2000);
}

Before
After
Reading a user from a database….
User { id: 1, gitHubname: ‘Jason’ }

当一个异步操作的结果 is ready,callback函数就会被调用并传入下面返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
console.log("Before");
const user = getUser(1, function (user) {
//get the repositories;
getRepositories(user.gitHubname, (repos) => {
console.log("Repos", repos);
});
});
console.log("After");

function getUser(id, callback) {
setTimeout(() => {
console.log("Reading a user from a database....");
callback({ id: id, gitHubname: "Jason" });
}, 2000);
}

function getRepositories(username, callback) {
setTimeout(() => {
console.log("Calling GitHub Api...");
callback(["repo1", "repo2", "repo3"]);
}, 2000);
}

Before
After

//两秒之后

Reading a user from a database….

//两秒之后

Calling GitHub Api…
Repos [ ‘repo1’, ‘repo2’, ‘repo3’ ]

Callback Hell

我们真实的代码可能远远比这个复杂。

1
2
3
4
5
6
7
8
9
10
console.log("Before");
const user = getUser(1, function (user) {
//get the repositories;
getRepositories(user.gitHubname, (repos) => {
getCommits(repo,(commits)=>{
//get...
})
});
});
console.log("After");

如果所有的异步代码都这样那还得了?全是很深的嵌套解构

我们把它叫做回调陷阱/回调地狱

Named Functions to Rescue

我们用 这种方式,用引用的方式调用函数。

调用getRepositories ,那么就再getRespositories中调用getCommits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.log("Before");
const user = getUser(1,getRepositories);
console.log("After");

function displayUser(user){
console.log(user);
}

function getRespositories(user){
getRepositories(user.gitHubUsername,getCommits);
}

function getCommits(repos){
getCommits(repos,displayCommits);
}
//...

Promises

promise就是一个用于保存异步操作结果的容器。当一个异步操作结束后,要么保存了值,要么保存了错误信息。promise就保证给你一个异步操作的结果。这个对象有三个states

1.当我创建了promise对象的时候,它处于Pending阶段。

2.当结果准备好,promise将被履行,也就是说异步操作成功完成,这样就得到一个值。进入Fulfiled阶段

3.否则如果在执行异步操作的过程当中出现了问题,promise进入Reject阶段

我们用下面这个例子来模拟异步操作成功了. resolve就代表了如果异步操作成功返回的值

然后p.then 就代表了成功以后进行的操作。运行以后我们看到两秒钟之后打印了Result 1

1
2
3
4
5
6
7
8
9
10
11
12
13
const p = new Promise((resolve, reject) => {
// Kick off some async work
//...
//
setTimeout(()=>{
resolve(1);
},2000);
// reject(new Error("message"));
});

p.then((result) => {
console.log("Result", result);
});

下面这个例子模仿了失败的异步操作,如果有错误,我们需要 p.catch来捕获这个错误,然后进行错误之后的动作

两秒之后我们得到了 Error message的打印信息

1
2
3
4
5
6
7
8
9
10
11
12
const p = new Promise((resolve, reject) => {
// Kick off some async work
//...
setTimeout(() => {
// resolve(1);
reject(new Error("message"));
}, 2000);

});

p.then((result) => console.log("Result", result))
.catch((err) =>console.log("Error", err.message));

Replacing Callbacks with Promises

现在我们用promise来取代callbacks

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
26
27
28
//...

function getUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Reading a user from a database....");
resolve({ id: id, gitHubname: "Jason" });
}, 2000);
});
}

function getRepositories(username) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Calling GitHub Api...");
resolve(["repo1", "repo2", "repo3"]);
}, 2000);
});
}

function getCommits(repo) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Calling GitHub Api...");
resolve(["commit"]);
}, 2000);
});
}

Consuming Promises

我们可以链式处理复杂的异步请求,也就是完成了第一步,then第二步,then第三步

但是不论何时使用Promise都需要我们捕捉错误

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
26
27
28
29
30
31
32
33
console.log('Before');
getUser(1)
.then(user=>getRespositories(user.getHubUsername))
.then(repos=>getCommits(repos[0]))
.then(commits=>console.log('Commits'),commits)
.catch(err=>console.log('Error'),err.message);

function getUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Reading a user from a database....");
resolve({ id: id, gitHubname: "Jason" });
}, 2000);
});
}

function getRepositories(username) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Calling GitHub Api...");
resolve(["repo1", "repo2", "repo3"]);
}, 2000);
});
}

function getCommits(repo) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Calling GitHub Api...");
resolve(["commit"]);
}, 2000);
});
}

Creating Settled Promises

有时候需要创建一个already resolved 的 promise 这在单元测试中比较常见

现在我们模仿一个请求服务器的异步请求成功完成,在单元测试中我们需要创建一个已经实现的Promise

1
2
3
4
const p = Promise.resolve({id:1});
p.then(result=>{
console.log(result);
})

有时候我们需要创建一个 already reject的promise,那么我们需要返回一个Error对象,因为里面包含了堆栈错误信息。

1
2
const p = Promise.reject(new Error('reason for rejection...'));
p.catch(error=>console.log(error))

Running Promises in Parallel

all

我这里有俩Promise对象,现在我想同时操作这两个Promises,当他们都做完了以后我们再去做我们想要的东西。所以我们调用Promise.all传入一个Promise数组。这里将返回一个新的Promise,他将在所有Promise对象全部resolve了以后才resolve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const p1 = new Promise((resolve) => {
setTimeout(() => {
console.log("Async operation 1..");
resolve(1);
}, 2000);
});
const p2 = new Promise((resolve) => {
setTimeout(() => {
console.log("Async operation 2..");
resolve(2);
}, 2000);
});
Promise.all([p1,p2]).then((result) => console.log(result));
.catch(err=>console.log(err.message));

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
2
Promise.race([p1,p2]).then((result) => console.log(result));
.catch(err=>console.log(err.message));

Async and Await

之前我们学习了用callback函数来处理异步请求。然后用Promise来重写了。但是我们可以用js的新特性。

async和await.它可以让我们像写同步操作一样写异步操作

但是我们还是需要有一个捕获错误的功能,这时候就需要 try和catch了

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
console.log("Before");
// getUser(1)
// .then(user=>getRespositories(user.getHubUsername))
// .then(repos=>getCommits(repos[0]))
// .then(commits=>console.log('Commits'),commits)
// .catch(err=>console.log('Error'),err.message);
//上面和下面两段代码表达的意思是一样的,但是风格不一样。
//下面的代码虽然写的像同步代码
async function displayCommits() {
try {
const user = await getUser(1);
const repos = await getRepositories(user.gitHubname);
const commits = await getCommits(repos[0]);
console.log(commits);
} catch (err) {
console.log("Error", err.message);
}
}
displayCommits();
console.log("after");
function getUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Reading a user from a database....");
resolve({ id: id, gitHubname: "Jason" });
}, 2000);
});
}

function getRepositories(username) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Calling GitHub Api...");
resolve(["repo1", "repo2", "repo3"]);
}, 2000);
});
}

function getCommits(repo) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Calling GitHub Api...");
resolve(["commit"]);
}, 2000);
});
}

Before
after
Reading a user from a database….
Calling GitHub Api…
Calling GitHub Api…
[ ‘commit’ ]

Exercise

用async和await改造这段代码

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
26
27
28
29
30
31
32
33
34
35

getCustomer(1, (customer) => {
console.log('Customer: ', customer);
if (customer.isGold) {
getTopMovies((movies) => {
console.log('Top movies: ', movies);
sendEmail(customer.email, movies, () => {
console.log('Email sent...')
});
});
}
});

function getCustomer(id, callback) {
setTimeout(() => {
callback({
id: 1,
name: 'Mosh Hamedani',
isGold: true,
email: 'email'
});
}, 4000);
}

function getTopMovies(callback) {
setTimeout(() => {
callback(['movie1', 'movie2']);
}, 4000);
}

function sendEmail(email, movies, callback) {
setTimeout(() => {
callback();
}, 4000);
}

答:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
async function notifyCustomer() {
try {
const customer = await getCustomer(1);
console.log("Customer", customer);
if (customer.isGold) {
const movies = getTopMovies();
console.log("Top movies:", movies);
sendEmail(customer.email, movies);
console.log("Email sent...");
}
} catch {}
}

notifyCustomer();

function getCustomer(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 1,
name: "Mosh Hamedani",
isGold: true,
email: "email",
});
}, 4000);
});
}

function getTopMovies() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["movie1", "movie2"]);
}, 4000);
});
}

function sendEmail(email, movies) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 4000);
});
}

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
2
3
4
5
const mongoose = require("mongoose");
mongoose
.connect("mongodb://localhost/playground")
.then(() => console.log("Connected to MongoDB"))
.catch((err) => console.log("Could not connect to MongoDB..."));

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
2
3
4
5
6
7
8
9
10
11
12
13
const mongoose = require("mongoose");
mongoose
.connect("mongodb://localhost/playground")
.then(() => console.log("Connected to MongoDB"))
.catch((err) => console.log("Could not connect to MongoDB..."));

const courseSchema = new mongoose.Schema({
name: String,
auther: String,
tags: [String],
date: { type: Date, default: Date.now },
isPublish:Boolean
});

Models

我们用courseSchema来定义数据库中course的文档结构,现在我们要把它弄成一个Model(模型)

在这里我们有一个Course,我们就可以创建一个类对象nodeCourse,然后我们就可以把nodeCourse保存到数据库当中。为了创建一个Course,我们可以把courseSchema变成Model

mongoose.model有两个参数,第一个参数是目标集合(单数)名称,也就是数据库中的collection集合;第二个参数是这个集合所需要的schema结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const mongoose = require("mongoose");
mongoose
.connect("mongodb://localhost/playground")
.then(() => console.log("Connected to MongoDB"))
.catch((err) => console.log("Could not connect to MongoDB..."));

const courseSchema = new mongoose.Schema({
name: String,
auther: String,
tags: [String],
date: { type: Date, default: Date.now },
isPublish:Boolean
});
const Course = mongoose.model('Course',courseSchema);//类
//新建一个对象
const course = new Course({
name: 'Node.js Course',
author:'Mosh',
tags:['node','backend'] ,
//date是有默认值的
isPublished: true
})

Saving a Document

保存到数据库是一个异步操作。因为保存到数据库得花点时间。因为我们要访问文件系统。

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
26
27
 const mongoose = require("mongoose");
mongoose
.connect("mongodb://localhost/playground")
.then(() => console.log("Connected to MongoDB"))
.catch((err) => console.log("Could not connect to MongoDB..."));

const courseSchema = new mongoose.Schema({
name: String,
auther: String,
tags: [String],
date: { type: Date, default: Date.now },
isPublish: Boolean,
});
const Course = mongoose.model("Course", courseSchema);
async function createCourse() {
//我们新建一个对象,然后利用model.save方法来把他保存到数据库当中。这个方法返回一个对象。我们打印
const course = new Course({
name: "Node.js Course",
author: "Mosh",
tags: ["node", "backend"],
//date是有默认值的
isPublished: true,
});
const result = await course.save();
console.log(result);
}
createCourse();

不像关系型数据库,我们不用创建表格,设计表格我们只要创建文档然后存进去就可以了。

现在我们多建几个信息

Querying Documents

现在我们来查询信息:

model.find()我可以得到一个文档的列表

model.findById()根据id找

model.findOne()返回一个单一的文档

我们也可以 model.find().then()

1
2
3
4
5
6
//...
async function getCourses() {
const courses = await Course.find();
console.log(courses);
}
getCourses();

这样我们就得到了两门课程

我们也可以加上一个过滤器,比如我们可以这样

1
2
3
4
5
async function getCourses() {
const courses = await Course.find({author:'Mosh',isPublished:true});
console.log(courses);
}
getCourses();

我们也可以

显示信息的数目,在括号中的数值

对数据进行排序,传入一个对象,里面的键值对用来进行排序。升序写1,降序写-1

对返回的列进行选择,传入一个对象 ,想得到name和tags 那么就写1,代表选中

1
2
3
4
5
6
7
8
async function getCourses() {
const courses = await Course.find({ author: "Mosh"})
.limit(10)
.sort({ name: 1 })
.select({ name: 1, tags: 1 });
console.log(courses);
}
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
2
3
const courses = await Course
.find({ price:{$gt : 10} })
console.log(courses);

那么我们如果想找出大于10 小于20 的课程呢?

1
2
3
const courses = await Course
.find({ price:{$gt : 10,$lte:20 } })
console.log(courses);

那么我如果想找10,15 或者20 dollar的课程呢? 我们利用数组

1
2
3
const courses = await Course
.find({ price:{$in:[10,15,20] } })
console.log(courses);

Logical Query Operators逻辑运算符

Course.find({author:'Mosh',isPublished:true}); 表示的是and,如果我们想要表示or 有应该怎么写呢?

我们可以先find()也就是全部找到

然后用.and 和 .or 来表达内部的逻辑是and还是or,传入的参数是一个对象数组,里面存放我们需要查找的对象

1
2
3
4
const courses = await Course
.find()
.or([{author:'Mosh'},{isPublished:true}])
.and([])

Regular Expressions

到现在为止我们得到的都是确定的字符串,但是我们想模糊查询,怎么办呢?

比如我想得到以Mosh开头的课程,我们可以这样写

\^ 是表示开头, $表示结尾 。如果不区分大小写

/i (忽略大小写)
/g (全文查找出现的所有匹配字符)
/m (多行查找)
/gi(全文查找、忽略大小写)
/ig(全文查找、忽略大小写)

如果我们只是想找出包含mosh的

1
2
3
4
const courses = await Course
.find({author: /^Mosh/ });
.find({author:/Hamedani$/i})
.find({author:/.*Mosh.*/i})

Counting

我们如果比较关心数量而不是对象本身,我们可以这样写

1
2
3
const courses = await Course
.find()
.count()

Pagination

与limit方法如影随形的是skip方法,常常用在分页器当中使用

为了实现分页,我们需要调过前面一页的所有文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function getCourses(){
const pageNumber = 2;
const pageSize = 10;
/*
在这里硬编码纯粹是为了简化,在现实中我们通过RESTful API 的查询字符串来输入这两个值
如果我们有一个获取课程列表的API
/api/course?pageNumber=2&pageSize=10
*/

const courses = await Course
.find({auther:"MOsh"})
.skip((pageNumber-1)*pageSize)
.limit(10);
.sort({name:1});
console.log(courses);
}

Exercise 1

导入数据后。我们要达到这个目标

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
26
27
28
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/mongo-exercises');

const courseSchema = new mongoose.Schema({
name: String,
author: String,
tags: [ String ],
date: Date,
isPublished: Boolean,
price: Number
});

const Course = mongoose.model('Course', courseSchema);

async function getCourses() {
return await Course
.find({ isPublished: true, tags: 'backend' })
.sort({ name: 1 })
.select({ name: 1, author: 1 });
}

async function run() {
const courses = await getCourses();
console.log(courses);
}

run();

Exercise 2

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
26
27
28
29
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/mongo-exercises');

const courseSchema = new mongoose.Schema({
name: String,
author: String,
tags: [ String ],
date: Date,
isPublished: Boolean,
price: Number
});

const Course = mongoose.model('Course', courseSchema);

async function getCourses() {
return await Course
.find({ isPublished: true},{tags:{$in:['frontend','backend']}})
.sort('-price')//不传入一个对象,我们也可以用字符串来代替
.select('name author');
}
//或者用or .or([{tags:'frontend'},{tags:'backend'}]) 效果是一样的

async function run() {
const courses = await getCourses();
console.log(courses);
}

run();

Exercise 3

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
26
27
28
29
30
31
32
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/mongo-exercises');

const courseSchema = new mongoose.Schema({
name: String,
author: String,
tags: [ String ],
date: Date,
isPublished: Boolean,
price: Number
});

const Course = mongoose.model('Course', courseSchema);

async function getCourses() {
return await Course
.find()
.or([
{author:/.*by.*/i},
{price:{$gte:15}}
])
.select('name author');
}
//或者用or .or([{tags:'frontend'},{tags:'backend'}]) 效果是一样的

async function run() {
const courses = await getCourses();
console.log(courses);
}

run();

Updating a Document- Query First

比如我有个ispublished=true的话,author不允许被修改的规则,那么我们可以这样修改if(course.isPublished) return ;

如何来更新数据库文档?

先来介绍 Query First 也就是先查询再更新

Approach:Update first

findById

Modify its properties

save()

1
2
3
4
5
6
7
8
9
async function updateCourse(id){
const course=await Course.findById(id);
if(!course) return ;
course.isPublished = true;
course.auther = 'Another Author'
const result = await course.save();
console.log(result)
}
updateCourse('5a68fdd7bee8ea64649c2777')

Updating a Document- Update First

我们也可以直接接入数据库修改文档

Approach:Update first

Update directly

Optionally: get the updated document

1
2
3
4
5
6
7
8
9
10
11
12
async function updateCourse(id){
//update()的第一个参数是一个filter,可以一次更新很多文档!
const result=await Course.update({_id:id},{
//这是实现更新操作的结果而非复合的文档,也就是说我们把满足条件的信息都进行修改
$set:{
author: 'Mosh',
isPublished:false
}
});
console.log(result)
}
updateCourse('5a68fdd7bee8ea64649c2777')

Removing Documents

怎么删除文档呢?

1
2
3
4
5
6
7
8
9
10
async function removeCourse(id){
//deleteOne()是删除一条信息,参数是一个过滤器,他将找到第一个符合对象的并且删除
const result = await Course.deleteOne({_id:id})
//deleteMany()删除符合条件的所有信息
const result = await Course.deleteMany({_id:id})
//如果我想得到被删除的文档,我可以使用findByIdAndRemove方法,如果给定的id不存在,就会返回null
const course = await Course.findByIdAndRemove({_id:id})
console.log(course);
}
removeCourse('5a68fdd7bee8ea64649c2777')

CRUD Operations with Mongoose and MongoDB Recap

-------------本文结束,感谢您的阅读-------------