nodeJs-04

52 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

同步API我们是可以直接用返回值获取结果的,但是异步API是不行的,异步API需要用回调函数的形式,用回调函数的形参接收返回结果

console.log(1)
// 同步代码在执行时,这个setTimout也会执行,只不过回调函数当前不会调用,会先推入到异步回调队列中,等达到执行条件了,就会调用
setTimout(() => {
  console.log(2)
}, 1000)
console.log(3)

回调地狱:指的是多个依次的异步操作,说白了就是回调函数嵌套,当嵌套层次过多,不利于阅读和维护

Promise基本使用:

const fs = require('fs')
// 这个resolve和reject是我们自己定义名字,你可以改成其他,但是不推荐,业内约定俗成就叫这个
const p = new Promise((resolve, reject) => {
  fs.readFile('./hehe.txt', 'utf8', (err, data) => {
    if (err) {
      reject(err)
    } else {
      resolve(data)
    }
  })
})

p.then((data) => {
  // 异步API执行正确,拿到正确的数据
	// your code . do something...
}).catch((err) => {
  // 异步API执行异常,拿到错误的数据
  // your code... do something...
})
  • Promise就是用来解决回调地狱的,,它不是一个新的技术,只是一种手段,是异步编程的中间产物
    • 原理:把一个异步API封装起来,将异步API的结果以回调函数的形式传递出去
  • 可以通过new Promise( ( resolve, reject ) => { ... }) 创建Promise实例
    • 注意:创建实例时,并不会执行( resolve, reject ) => { ... }回调函数,也就意味着内部的代码都不会执行,只有在调用then或catch方法的时候才会去调用
  • 可以通过resolve( result )方法传递正确的结果
  • 可以通过reject( err )方法传递错误的结果
  • 通过调用Promise实例的then(( result ) => { ... })方法接收正确的结果
  • 通过调用Promise实例的catch(( err ) => { ... })方法接收错误的结果

解决回调地狱:

const fs = require('fs')

// 想要依次读取1.txt,2.txt,3.txt的文件内容

// fs.readFile('./1.txt', 'utf8', (err, data) => {
//   console.log(data)
//   fs.readFile('./2.txt', 'utf8', (err, data) => {
//     console.log(data)
//     fs.readFile('./3.txt', 'utf8', (err, data) => {
//       console.log(data)
//     })
//   })
// })

// new Promise(cb) 实例时,并不会调用这个 cb 函数
// 只有在调用了Promise实例的then方法或catch方法,才会调用cb函数
const p1 = new Promise((yes, no) => {
  fs.readFile('./1.txt', 'utf8', (err, data) => {
    yes(data)
  })
})

const p2 = new Promise((yes, no) => {
  fs.readFile('./2.txt', 'utf8', (err, data) => {
    yes(data)
  })
})

const p3 = new Promise((yes, no) => {
  fs.readFile('./3.txt', 'utf8', (err, data) => {
    yes(data)
  })
})

// then函数的返回值 就是 其参数(回调函数)的返回值
p1.then(data => {
  console.log(data)
  return p2
}).then(data => {
  console.log(data)
  return p3
}).then(data => {
  console.log(data)
})

异步函数(ES7添加的):

  • async关键字:
    • 在普通函数前面加上async关键字,该普通函数就变成了异步函数
    • 异步函数的返回值会被包装成一个Promise实例
      • return传递正确的结果,相当于Promise里面的resolve函数
      • throw传递错误的结果,相当于Promise里面的reject函数,一旦抛出错误,后面的代码就不会执行了
  • await关键字:
    • await后面一般跟Promise实例(其实也可以跟其他的数据,如果是其他的数据,则直接返回),await可以阻塞后续的代码,等待Promise实例返回结果后,再继续向下执行
    • await可以直接拿到Promise实例的正确结果,错误结果需要用try ... catchcatch(err => err)(推荐用这个)来获取
    • await只能出现在异步函数(亲爸爸)中

范例:用async、await关键字改造依次读取1.txt、2.txt、3.txt文件的代码:

const fs = require('fs')
// promiseify可以将node中现有的基于回调函数形式的异步API,改造成返回Promise实例的函数
const { promiseify } = require('util')

// fs.readFile('./1.txt', 'utf8', (err, data) => { ... })
const readFileOfPromise = promiseify(fs.readFile)

(async () => {
 	const data1 = await readFileOfPromise('./1.txt', 'utf8')
  console.log(data1)
  const data2 = await readFileOfPromise('./2.txt', 'utf8')
  console.log(data2)
  const data3 = await readFileOfPromise('./3.txt', 'utf8')
  console.log(data3)
})()

数据库概述:

  • 数据库就是存储数据的仓库,可以对数据进行分类存储,并提供强大的操作数据的方法,是一个独立的软件
  • 常用的数据库有:mysql、mongodb、orcle。。。
  • mongodb是我们前端上手最快的数据库,我们先学习数据库相关的概念,日后再学习其他数据库时就会很轻松

数据库概念:

  • 数据库(database):一个数据库软件中可以创建多个数据库,每个数据库都是独立的存储数据的仓库
  • 集合(collection):集合是某个数据库中的一类数据的集合(可以理解为JS中的数组)[ {}, {}, {}, {} ]
  • 文档(document):是集合中一条具体的数据(可以理解为数组中的对象){ name: 'zs', age: 18 }
  • 字段(field):是文档中具体的某个属性(可以理解为数组中的对象的某个键值对)name: 'zs'

使用nodejs连接数据:

  • 启动数据库:打开命令行,执行:net start mongodbmongod(Mac)

  • 停止数据库:打开命令行,执行:net stop mongodbmongod(Mac)

  • 使用第三方模块mongoose去操作数据库,范例:

    const mongoose = require('mongoose')
    
    mongoose.connect('mongodb://localhost:27017/dbname').then(() => {
      console.log('数据库连接成功')
    }).catch(err => {
      console.log('数据库连接失败:', err)
    })
    

    补充说明:如果你连接的数据库不存在,mongodb会在第一次插入数据的时候自动给我们创建

创建集合及插入文档:

  • 创建集合,分为两步:

    • 创建集合规则
    • 应用集合规则,创建集合
  • 创建文档,分为两步:

    • new 集合() 创建文档实例
    • 调用save方法保存
  • 范例:

    const mongoose = require('mongoose')
    
    mongoose
      .connect('mongodb://localhost:27017/test01')
      .then(() => {
        console.log('数据库连接成功')
      })
      .catch(err => {
        console.log(err)
      })
    
    const userSchema = new mongoose.Schema(
      {
        name: String,
        age: Number,
        isMarry: Boolean
      },
      {
        versionKey: false
      }
    )
    
    const User = mongoose.model(
      'User' /* 是JS在运行时,内存中的集合名称,可以和数据库中真正的集合名称不一致,但是一般都是保持一致的 */,
      userSchema,
      'User' /* 指定数据库里的集合名称,如果不指定,默认会改成小写的复数形式 */
    )
    
    const ls = new User({
      name: 'ls',
      age: 18,
      isMarry: false
    })
    
    ls.save()
    
  • 插入文件的另一种方式:可以使用集合构造函数的create方法来插入文档(推荐用这个),范例:

    // [ 回调函数形式
    User.create({
      name: 'ww',
      age: 19,
      isMarry: true
    }, (err, user) => {
      cnosole.log(user)
    })
    // ] 回调函数形式
    
    // [ Promise 形式
    User.create({
      name: 'ww',
      age: 19,
      isMarry: true
    }).then(doc => {
      console.log(doc)
    })
    // ] Promise 形式
    
    // [ async await 形式 (推荐)
    ;(async () => {
      const ww = await User.create({
        name: 'zl',
        age: 59,
        isMarry: true
      })
      console.log(ww)
    })()
    // ] async await 形式
    

如果你写代码时,不喜欢加分号,那么在自执行函数前面是需要加分号的

向数据库中导入数据:

  • 导入数据:mongoimport -d 数据库名称 -c 集合名称 --file 要导入的文件路径
  • 注意:mongodb安装目录的bin目录下存放的是mongodb相关的可执行文件,需要加入到path系统环境变量中,才阔以在命令行中执行命令

查询文档:

  • find():查找多个,返回的是数组,不管有多少个,返回的都是数组

  • findOne():查找一个,返回的是一个文档,如果匹配到多条,返回第一条

  • 共同点:它们都返回Promise对象,既然返回的是Promise对象,那么就推荐使用async/await关键字去调用,都可以带查询条件

  • 大于、小于:

    User.find({ age: { $gt: 18, $lt: 40 } }).then(users => console.log(users))
    
  • 包含:

    User.find({ hobbies: { $in: ['敲代码'] } }).then(users => console.log(users))
    
  • 筛选字段(_id会默认带上,如果不要在前面加个-):

    User.find().select('name age').then(users => console.log(users))
    
  • 排序:

    // 升序
    User.find().sort('age').then(users => console.log(users))
    // 降序
    User.find().sort('-age').then(users => console.log(users))
    
  • 分页,跳过n条(skip),查n条(limit)

    // 获取第4页的数据
    User.find().skip(3 * 10).limit(10).then(users => console.log(users))
    

删除文档:

  • 删除单个:

    User.findOneAndDelete({ 条件 }).then...
    
    • 返回的是被删除的文档
    • 如果匹配到多条,删除第一条
  • 删除多个:

    User.deleteMany({ 一定不要忘记带条件,如果不带就是无条件删除该集合中所有数据 }).then...
    
    • 返回值是本次删除操作的信息对象,形如:{ n: 2, ok: 1, deletedCount: 2 }
      • n:表示匹配的条数
      • ok:表示本次删除操作是否执行成功,1:成功,0:失败
      • deletedCount:代表已经被删除的条数
      • 补充说明:当本次删除操作异常时,n和deletedCount就有可能不一致

更新文档:

  • 更新单个:

    User.updateOne({ 条件 }, { 要更新的值 }).then...
    
    • 返回值是更新信息对象,形如:{ n: 1, nModified: 1, ok: 1 }
      • n:表示匹配的条数
      • nModified:表示已经更新的条数
      • ok:表示本次更新操作是否执行成功,1:成功,0:失败
  • 更新多个:

    User.updateMany({ 条件 }, { 要更新的值 }).then...
    
    • 返回值和updateOne一样