二、包、文件系统

1. 包

1.1 什么是包

包规范允许我们将一组相关的模块组合到一起,形成一组完整的工具。

包规范由 包结构 和 包描述文件 两个部分组成。

包结构:用于组织包中的各种文件。

包描述文件:描述包的相关信息,以供外部读取分析。

1.2 包结构

包实际上就是一个压缩文件,解压以后还原目录。符合规范的目录,应该包含如下文件:

1.package.json 描述文件

2.bin 可执行二进制文件

2.lib js代码

3.doc 文档

4.test 单元测试

1.3描述文件

package.json => 项目描述文件,记录了当前项目信息,例如项目名称、版本、作者、github地址、当前项目依赖了哪些第三方模块等。使用npm init -y命令生成

项目依赖:在项目的开发阶段和线上运营阶段,都需要依赖的第三方包,称为项目依赖。

使用npm install 包名命令下载的文件会默认被添加到 package.json 文件的 dependencies 字段中。

在项目的开发阶段需要依赖,线上运营阶段不需要依赖的第三方包,称为开发依赖。

使用npm install 包名 --save-dev命令将包添加到package.json文件的devDependencies字段中。

dependencies 依赖当前包依赖的其他包       devDependencies 开发环境依赖

package-lock.json文件的作用:锁定包的版本,确保再次下载时不会因为包版本不同而产生问题。

加快下载速度,因为该文件中已经记录了项目所依赖第三方包的树状结构和包的下载地址,重新安装时只需下载即可,不需要做额外的工作。

1.4 NPM

1.4.1(Node Package Manager)

npm 帮助其完成了第三方模块的发布、安装和依赖等。借助NPM,Node与第三方模块之间形成了很好的一个生态系统。

命令:

1.npm -v :查看npm的版本

2.npm version :查看所有模块的版本

3.npm :帮助说明

4.npm search 包名 :搜索模块包

5.npm install/i 包名 :在当前目录安装包

6.npm install/i 包名 -g :全局模式安装包 (全局安装的包一般都是一些工具)

7.npm remove/r 包名 :删除一个模块

8.npm uninstall 包名 :删除一个模块

9.npm update :升级全部包

10.npm update 包名 :升级指定包

11.npm install 包名 --save :安装包并添加到依赖中 

12.npm install :下载当前项目所依赖的包

13.npm install 文件路径 :从本地安装

14.npm install 包名 --registry=地址 :从镜像源安装

15.npm config set registry 地址 :设置镜像源

16.npm install -g 就是npm install --global

17.npm install -D 就是npm install --save-dev

(1) devDependencies 里面的包只用于开发环境,不用于生产环境

18.npm install -S 就是npm install --save

(1) 而 dependencies 是需要发布到生产环境的

19.npm view 包名 versions 查看所有版本 /npm view 包名 version 查看最高版本

20.npm i 包名@版本 安装指定版本的包

全局安装与本地安装:

1.命令行工具:全局安装

2.库文件:本地安装

1.4.2 cnpm中国 npm 镜像的客户端

1.npm install cnpm -g :安装cnpm(cnpm 是中国 npm 镜像的客户端)

2.npm install cnpm -g --registry=https://registry.npm.taobao.org (国内安装)

1.4.3 npm修改或切换镜像地址

方式一:直接编辑npm的配置文件 => npm config edit => 直接修改registry的地址

registry=https://registry.npm.taobao.org

方式二:用代码更改npm的配置文件 => npm config set registry http://registry.npm.taobao.org

这段代码即将镜像改为淘宝镜像

方式三:使用nrm管理registry地址  安装nrm npm install -g nrm

查看镜像列表  nrm ls  nrm ls 报错解决方案:

//const NRMRC = path.join(process.env.HOME, '.nrmrc');(注掉)

const NRMRC = path.join(process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'], '.nrmrc');

切换镜像  nrm use taobao

在nrm添加自己的镜像地址   nrm add r_name r_url

删除   nrm del r_name

测试镜像的相应速度   nrm test r_namer

1.4.4 yarn

方式一:使用安装包安装

官方下载安装包,https://yarnpkg.com/zh-Hans/docs/install,安装完毕后,一定要配置环境变量。

方式二:使用npm安装  => npm i yarn -g

常用命令:

1.yarn / yarn install 等同于npm install 批量安装依赖

2.yarn add xxx 等同于 npm install xxx --save 安装指定包到指定位置

3.yarn remove xxx 等同于 npm uninstall xxx --save 卸载指定包

4.yarn add xxx --dev 等同于 npm install xxx --save-dev

5.yarn upgrade 等同于 npm update 升级全部包

6.yarn global add xxx 等同于 npm install xxx -g 全局安装指定包

1.4.5 npx

1.npm 从5.2版开始,增加了 npx 命令。Node 自带 npm 模块,所以可以直接使用 npx 命令。万一不能用,就要手动安装一下。npm install -g npx

2.调用项目安装的模块:npx 想要解决的主要问题,就是调用项目内部安装的模块。

3.npm install -D mocha   一般来说,调用 Mocha ,只能在项目脚本和 package.json 的scripts字段里面, 如果想在命令行下调用,必须像下面这样。node-modules/.bin/mocha --version

npx 就是想解决这个问题,让项目内部安装的模块用起来更方便,只要像下面这样调用就行了。

npx mocha --version   npx 的原理很简单,就是运行的时候,会到node_modules/.bin路径和环境变量$PATH里面,检查命令是否存在。

4.由于 npx 会检查环境变量$PATH,所以系统命令也可以调用。npx ls

注意,Bash 内置的命令不在$PATH里面,所以不能用。比如,cd是 Bash 命令,因此就不能用npx cd。

5.避免全局安装模块:除了调用项目内部模块,npx 还能避免全局安装的模块。比如,create-react-app这个模块是全局安装,npx 可以运行它,而且不进行全局安装。

npx create-react-app my-react-app

上面代码运行时,npx 将create-react-app下载到一个临时目录,使用以后再删除。所以,以后再次执行上面的命令,会重新下载create-react-app。

6.下载全局模块时,npx 允许指定版本。npx uglify-js@3.1.0 main.js -o ./dist/main.js

上面代码指定使用 3.1.0 版本的uglify-js压缩脚本。

注意,只要 npx 后面的模块无法在本地发现,就会下载同名模块。比如,本地没有安装http-server模块,下面的命令会自动下载该模块,在当前目录启动一个 Web 服务。npx http-server

7.--no-install 参数和--ignore-existing 参数:

如果想让 npx 强制使用本地模块,不下载远程模块,可以使用--no-install参数。如果本地不存在该模块,就会报错。

npx --no-install http-server

反过来,如果忽略本地的同名模块,强制安装使用远程模块,可以使用--ignore-existing参数。比如,本地已经全局安装了create-react-app,但还是想使用远程模块,就用这个参数。

npx --ignore-existing create-react-app my-react-app

使用不同版本的 node:

利用 npx 可以下载模块这个特点,可以指定某个版本的 Node 运行脚本。它的窍门就是使用 npm 的 node 模块。

npx node@0.12.8 -v

上面命令会使用 0.12.8 版本的 Node 执行脚本。原理是从 npm 下载这个版本的 node,使用后再删掉。

某些场景下,这个方法用来切换 Node 版本,要比 nvm 那样的版本管理器方便一些。

8.-p 参数:

-p参数用于指定 npx 所要安装的模块,所以上一节的命令可以写成下面这样。

npx -p node@0.12.8 node -v

上面命令先指定安装node@0.12.8,然后再执行node -v命令。

-p参数对于需要安装多个模块的场景很有用。npx -p lolcatjs -p cowsay [command]

9.-c 参数:

如果 npx 安装多个模块,默认情况下,所执行的命令之中,只有第一个可执行项会使用 npx 安装的模块,后面的可执行项还是会交给 Shell 解释。

npx -p lolcatjs -p cowsay 'cowsay hello | lolcatjs'

上面代码中,cowsay hello | lolcatjs执行时会报错,原因是第一项cowsay由 npx 解释,而第二项命令localcatjs由 Shell 解释,但是lolcatjs并没有全局安装,所以报错。

-c参数可以将所有命令都用 npx 解释。有了它,下面代码就可以正常执行了。

npx -p lolcatjs -p cowsay -c 'cowsay hello | lolcatjs'

-c参数的另一个作用,是将环境变量带入所要执行的命令。举例来说,npm 提供当前项目的一些环境变量,可以用下面的命令查看。

npm run env | grep npm_

-c参数可以把这些 npm 的环境变量带入 npx 命令。

npx -c 'echo "$npm_package_name"'

上面代码会输出当前项目的项目名。

执行 GitHub 源码:

npx 还可以执行 GitHub 上面的模块源码。

执行 Gist 代码

npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32

执行仓库代码

npx github:piuccio/cowsay hello

注意,远程代码必须是一个模块,即必须包含package.json和入口脚本。

1.5 Node.js中模块加载机制

模块查找规则-当模块拥有路径但没有后缀时:require('./find.js');  require('./find');

1.require方法根据模块路径查找模块,如果是完整路径,直接引入模块。

2.如果模块后缀省略,先找同名JS文件再找同名JS文件夹。

3.如果找到了同名文件夹,找文件夹中的index.js。

4.如果文件夹中没有index.js就会去当前文件夹中的package.json文件中查找main选项中的入口文件。

5.如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有被找到。

模块查找规则-当模块没有路径且没有后缀时:require('find');

1.Node.js会假设它是系统模块。

2.Node.js会去node_modules文件夹中。

3.首先看是否有该名字的JS文件。

4.再看是否有该名字的文件夹。

5.如果是文件夹看里面是否有index.js。

6.如果没有index.js查看该文件夹中的package.json中的main选项确定模块入口文件。

7.否则找不到报错。

2. 文件系统

2.1 Buffer(缓冲区)

Buffer的结构和数组很像,操作的方法也和数组类似。数组中不能存储二进制文件,而Buffer就是专门用来存储二进制数据使用buffer不需要引入模块,直接使用即可。在buffer中存储的都是二进制数据,但是在显示时都是以16进制的形式显示。

buffer中每一个元素的范围是从 00 - ff 0 - 255 => 00000000 - 11111111

计算机中 一个0 或 一个1 我们称为1位(bit)

8bit = 1byte(字节)1024byte = 1kb   1024kb = 1mb   1024mb = 1gb     1024gb = 1tb

buffer中的一个元素,占用内存中的一个字节

buffer的大小一旦确定,则不能修改,buffer实际上是对底层内存的直接操作

Buffer.from(str) 将一个字符串转换位为Buffer

Buffer.alloc(size) 创建一个指定大小的Buffer(并将内存中的数据清空)

Buffer.allocUnsafe(size) 创建一个指定大小的Buffer,但是可能包含敏感数据(分配空间的时候,没有清空里面的数据,性能会好一些)

buf.toString() 将缓冲区中的数据转换为字符串    buf.length 占用内存的大小

在UTF-8编码中,1个汉字占用3个字节

buf[2] = 0xaa 十六进制数,以0x开头

buf[3] = 556 只能保存8位,前面的会省略掉

buf[2].toString(16) 转16进制

buf3[2].toString(2) 转2进制

// 创建一个长度为20的Buffer空间,(Buffer是nodejs环境中的缓冲区空间)
// 一个Buffer空间空间,就是一个字节空间,一个字节空间是8位(8bit)。
// 计算机中 一个0 或 一个1 我们称为1位(bit)
/* 
    8bit = 1byte(字节)
    1024byte = 1kb
    1024kb = 1mb
    1024mb = 1gb
    1024gb = 1tb
*/
let bf = Buffer.alloc(20)
// Buffer空间里面只能存储二进制数据,所以,65会转为二进制存储
bf[0] = 65
// 一个字节空间,所能存储的最大数据就是255,因为255对应的二进制数据是:11111111
bf[1] = 66
// 因为二进制的数据比较长,在展示数据时,Buffer会将二进制格式的数据转为十六进制显示
console.log(bf); //<Buffer 41 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>
// 将Buffer空间里面保存的数据转为字符串。
console.log(bf.toString()); //AB

2.2 文件系统

文件系统简单来说就是通过node来操作系统中的文件,使用文件系统,需要先引入fs模块,fs是核心模块,直接引入不需要下载。fs模块中所有的操作都有两种形式可供选择 同步和异步。

同步文件系统会阻塞程序的执行,也就是除非操作完毕,否则不会向下执行代码。

异步文件系统不会阻塞程序的执行,而是在操作完成时,通过回调函数将结果返回。

2.3 同步文件的写入

步骤:1.打开文件

fs.openSync(path[, flags, mode])

- path 要打开文件的路径    - flags 打开文件要做的操作的类型

- r 只读的   - w 可写的     - mode 设置文件的操作全选,一般不传

返回值:该方法会返回一个文件的描述符作为结果,我们可以通过该描述符来对文件进行各种操作

2.向文件中写入内容

fs.writeSync(fd, string[, position[, encoding]])

- fd 文件的描述符,需要传递要写入的文件的描述符   - string 要写入的内容

- position 写入的起始位置    - encoding 写入的编码,默认utf-8

3.保存并关闭文件

fs.closeSync(fd)    - fd 要关闭的文件的描述符

// 导入path系统模块
const path = require('path')
// 该模块中的join()方法,用于拼接路径
let url = path.join(__dirname, 'a', 'b', 'c', 'd') 
console.log(url);     //D:\桌面\node.js\L03\a\b\c\d

// 文件系统
// 01导入文件系统模块,它是系统模块
const fs = require('fs')

// 02.打开文件;openSync()方法,采用的是同步方式打开文件
// openSync()方法,需要传两个参数:文件路径,操作类型(r是读取,w是写入)
// openSync()方法,返回一个文件的描述符作为结果
let fd = fs.openSync('./data/demo1.txt', 'r')
// 03.读取文件中的数据;readSync()方法,采用的是同步方式读取文件,返回值是读取的长度
let bf = Buffer.alloc(20)
let num = fs.readSync(fd, bf) 
console.log(num);    // 12
// 注意:在nodejs环境中,一个汉字占3个字节,换行符占2个字节
console.log(bf);  //<Buffer e4 bd a0 e5 a5 bd e4 b8 96 e7 95 8c 00 00 00 00 00 00 00 00>
console.log(bf.toString());   //你好世界
// 04.关闭文件
fs.closeSync(fd)
// 写文件
// 01.导入文件系统模块
const fs = require('fs')
// 02.打开文件,第二个参数是打开方式,w是写,r是读
const fd = fs.openSync('./data/demo2.txt','w')
// 03.写文件
fs.writeSync(fd,'好好学习,天天向上')
// 04.关闭文件
fs.closeSync(fd)

2.4 异步文件写入

步骤:1.打开文件

fs.open(path[, flags[, mode]], callback) -用来异步打开一个文件

​异步调用的方法,结果都是通过回调函数的参数返回的

​回调函数两参数:err :错误对象,如果没有错误则为null  ,   ​fd :文件的描述符

2.向文件中写入内容

fs.write(fd, buffer[, offset[, length[, position]]], callback) - 用来异步写入一个文件

3.保存并关闭文件

fs.close(fd, callback)- 用来关闭文件

// 文件系统
const fs = require('fs')
// 采用异步方式读取文件,注意:这里所有的异步方法都是采用回调函数返回结果
fs.open('./data/demo1.txt',(err,fd)=>{
    //表示打开文件成功
    if(!err){
        fs.read(fd,(err,num,bf)=>{
           if(!err){
               console.log('文件1:'+bf.toString());
               fs.close(fd,(err)=>{
                   if(!err){
                    // console.log('文件1关闭成功!');
                   }
               })
           }
        })
    }
})
fs.open('./data/demo2.txt',(err,fd)=>{
    //表示打开文件成功
    if(!err){
        fs.read(fd,(err,num,bf)=>{
           if(!err){
               console.log('文件2:'+bf.toString());
               fs.close(fd,(err)=>{
                   if(!err){
                    // console.log('文件2关闭成功!');
                   }
               })
           }
        })
    }
})
fs.open('./data/demo3.txt',(err,fd)=>{
    //表示打开文件成功
    if(!err){
        fs.read(fd,(err,num,bf)=>{
           if(!err){
               console.log('文件3:'+bf.toString());
               fs.close(fd,(err)=>{
                   if(!err){
                    // console.log('文件3关闭成功!');
                   }
               })
           }
        })
    }
})

结果: 

文件3:天天向上
文件2:好好学习,天天向上
文件1:你好世界

// 读取文件(异步)
// 01.导入fs模块
const fs = require('fs')
// 02.读取文件
// 直接通过readFile()方法,读取文件,它会自动打开文件和关闭文件
fs.readFile('./data/img/zjl.jpg', (err, bf) => {
    if (!err) {
        // Buffer.alloc()方法可以传递两个参数
        // 第一个参数是长度,第二参数是数据
        // bf = Buffer.alloc(8000,bf)
        // 没有错误,表示读取到数据
        // console.log(bf.toString());
        // 直接通过writeFile()方法,写入文件,它会自动打开文件和关闭文件
        fs.writeFile('./data/img/zjl2.jpg', bf, (err) => {
            if (!err) {
                console.log('文件复制成功!');   //文件复制成功!
            }
        })
    }
})

2.5 简单文件写入

fs.writeFileSync(file, data[, options])

fs.writeFile(file, data[, options], callback)

​- file 要操作的文件的路径    - data 要写入的数据     - options 选项,可以对写入进行一些设置

​- flag : r 只读 / w 可写 / a 追加    - callback 当写入完成以后执行的函数

步骤:

var fs = require('fs')  => 1.引入fs模块 
fs.writeFile('C:\Users\bing\Desktop\hello.txt','内容',{flag:'a'},function(err){ => 2.写入
   if(!err){
​     console.log('写入成功~~~');
    }else{
​     console.log(err);
   }
})

2.6 流式文件写入

同步,异步,简单文件的写入都不适合大文件的写入,性能较差,容易导致内存溢出

流式文件写入步骤:

1.创建一个可写流

var ws = fs.createWriteStream(path[, options])

​ - 可以用来创建一个可写流

​ - path,文件路径

​ - options,配置的参数

2.可以通过监听流的open和close事件来监听流的打开和关闭

(1) on 和 once 绑定事件

(2) once 可以为对象绑定一个一次性的事件,该事件将会在触发一次以后自动失效

ws.once('open',function(){
console.log('流打开了~~~');
})
ws.once('close',function(){
console.log('流关闭了~~~');
})

3.通过write方法向文件中输出内容

ws.write('通过可写流写入文件的内容')

ws.write('helloworld')

4.关闭流

ws.end()

2.7 简单文件读取

fs.readFileSync(path[, options])

fs.readFile(path[, options], callback)

​ - path 要读取的文件的路径

​ - options 读取的选项

​ - callback 回调函数,通过回调函数将读取的内容返回(err,data)

​ err 错误对象  data 读取到的数据,会返回一个Buffer

步骤:

var fs = require('fs') => 1.引入fs模块
fs.readFile('gxb.jpg',function(err,data){ => 2.读取
            if(!err){
​            // console.log(data);
​           //将data写入到文件中
​     fs.writeFile('hello.jpg',data,function(err){
​           if(!err){
​           console.log('文件写入成功');
​          }
​     })
          }
})

2.8 流式文件读取

流式文件读取也适用于一些较大的文件,可以分多次将文件读取到内存中

步骤1:

var fs = require('fs') //引入fs模块
var rs = fs.createReadStream('gxb.jpg') //创建一个可读流
var ws = fs.createWriteStream('hello2.jpg') //创建一个可写流
//监听流的开启和关闭
rs.once('open',function(){
console.log('可读流打开了~~');
})
rs.once('close',function(){
console.log('可读流关闭了~~');
//数据读取完毕,关闭可写流
ws.end()
})
ws.once('open',function(){
console.log('可写流打开了~~');
})
ws.once('close',function(){
console.log('可写流关闭了~~');
})

//如果要读取一个可读流中的数据,必须要为可读流绑定一个data事件,

//data事件绑定完毕,它会自动开始读取数据

//注意:流式文件读取对象,并不是一次性读取文件的全部数据,而是分多次读取。

//所以,监听data事件,就不能使用once,必须使用on

//once只监听一次事件,on可以连续监听。

rs.on('data',function(data){
// console.log(data);
// console.log(data.length);
// 将读取到的数据写入到可写流中
ws.write(data)
})

步骤2:

var fs = require('fs') //引入fs模块
var rs = fs.createReadStream('gxb.jpg') //创建一个可读流
var ws = fs.createWriteStream('hello3.jpg') //创建一个可写流
//可以将可读流中的内容,直接输出到可写流中
//相当于在这两个对象之间建立的管道,可以直接传输数据。
rs.pipe(ws)

2.9 fs中其他方法

1.fs.existsSync(path)     - 检查一个文件是否存在

2.fs.stat(path[, options], callback)      fs.statSync(path[, options])     - 获取文件的状态

- callback 回调函数,它会给我们返回一个对象,这个对象中保存了当前对象状态的相关信息

3.fs.unlink(path, callback)   fs.unlinkSync(path)   - 删除文件

4.fs.readdir(path[, options], callback)    fs.readdirSync(path[, options]) - 读取一个目录的目录结构

- files 是一个字符串数组,每一个元素就是一个文件夹或文件的名字

5.fs.truncate(path[, len], callback)  fs.truncateSync(path[, len])- 截断文件,将文件修改为指定的大小

6.fs.mkdir(path[, options], callback)  fs.mkdirSync(path[, options])   - 创建一个目录

7.fs.rmdir(path[, options], callback)  fs.rmdirSync(path[, options])   - 删除一个目录

8.fs.rename(oldPath, newPath, callback)  fs.renameSync(oldPath, newPath) - 对文件进行重命名

oldPath 旧的路径   newPath 新的路径    callback 回调函数

9.fs.watchFile(filename[, options], listener)   - 监视文件的修改

filename 要监视的文件的名字  options 配置选项

listener 回调函数,当文件发生变化时,回调函数会执行

在回调函数中会有两个参数: curr 当前文件的状态   prev 修改前文件的状态 

这两个对象都是stats对象

2.10 异步编程

2.10.1 同步API, 异步API

同步API:只有当前API执行完成后,才能继续执行下一个API

console.log('before');

console.log('after');

异步API:当前API的执行不会阻塞后续代码的执行

console.log('before');

setTimeout(

() => { console.log('last');

}, 2000);

console.log('after');

2.10.2 同步API, 异步API的区别( 获取返回值 )

同步API可以从返回值中拿到API执行的结果, 但是异步API是不可以的

// 同步
function sum (n1, n2) {
   return n1 + n2;
}
const result = sum (10, 20);
// 异步
function getMsg () {
setTimeout(function () {
​ return { msg: 'Hello Node.js' }
  }, 2000);
}
const msg = getMsg ();
// 同步API和异步API
// 定义一个同步API(在方法内部,直接返回结果)
function fun1(num1, num2) {
    return num1 + num2
}
let sum1 = fun1(100, 200)  
console.log('sum1=' + sum1);  //sum1=300
// 定义一个异步API(利用回调函数返回结果)
function fun2(num1, num2, callback) {
    let sum = 0;
    //使用定时器模拟异步操作
    setTimeout(() => {
        sum = num1 + num2
        callback(sum)
    }, 500);
}
fun2(100, 200, sum2 => {
    console.log('sum2=' + sum2);  //sum2=300
})

2.10.3 回调函数

自己定义函数让别人去调用。

// getData函数定义
function getData (callback) {}
// getData函数调用
getData (() => {});
//使用回调函数获取异步API执行结果
function getMsg (callback) {
setTimeout(function () {
​ callback ({ msg: 'Hello Node.js' })
}, 2000);
}
getMsg (function (msg) {
console.log(msg);
});

2.10.4 同步API, 异步API的区别(代码执行顺序)

//同步API从上到下依次执行,前面代码会阻塞后面代码的执行
for (var i = 0; i < 100000; i++) {
console.log(i);
}
console.log('for循环后面的代码');
//异步API不会等待API执行完成后再向下执行代码
console.log('代码开始执行');
setTimeout(() => { console.log('2秒后执行的代码')}, 2000);
setTimeout(() => { console.log('"0秒"后执行的代码')}, 0);
console.log('代码结束执行');
// 文件系统
const fs = require("fs");

//方式1:采用同步方式读取文件(同步API通过返回值返回结果)
function fun1() {
  // readFileSync是一个同步方法
  let bf1 = fs.readFileSync("./data/demo1.txt");
  console.log(bf1.toString());
  console.log("执行后续操作...");
}
fun1();
console.log("------------------------------");
//方式2:采用异步方式读取文件(异步API通过回调函数返回结果)
function fun2() {
  fs.readFile("./data/demo1.txt", (err, bf2) => {
    console.log(bf2.toString());
  });
  console.log("执行后续操作...");
}
fun2();

结果:

世界那么大,我想去看看!
执行后续操作...
------------------------------
执行后续操作...
世界那么大,我想去看看!

2.10.5 Node.js中的异步API

fs.readFile('./demo.txt', (err, result) => {});
var server = http.createServer();
server.on('request', (req, res) => {});

如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题要怎么解决呢?

fs.readFile('./demo.txt', (err, result) => {});

console.log('文件读取结果');

需求:依次读取A文件、B文件、C文件

2.10.6 Promise

Promise出现的目的是解决Node.js异步编程中回调嵌套过深的问题。

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
​      if (true) {
​        resolve({name: '张三'})
​      }else {
​        reject('失败了')
​      }
    }, 2000);
});
promise.then(result => 
console.log(result); 
// {name: '张三'}
).catch(error =>
 console.log(error); 
// 失败了)

2.10.7 异步函数

异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。

const fn = async () => {};    async function fn () {}

async关键字:

1.普通函数定义前加async关键字 普通函数变成异步函数

2.异步函数默认返回promise对象

3.在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法

4.在异步函数内部使用throw关键字抛出程序异常

5.调用异步函数再链式调用then方法获取异步函数执行结果

6.调用异步函数再链式调用catch方法获取异步函数执行的错误信息

await关键字:

1.await关键字只能出现在异步函数中

2.await promise await后面只能写promise对象 写其他类型的API是不不可以的

3.await关键字可是暂停异步函数向下执行 直到promise返回结果

const fs = require("fs");
function fun1() {
    //下面的代码,回调函数嵌套的层级太深,这样的代码称作:回调地狱
    fs.readFile("./data/file/province.txt", (err, data) => {
        if (data == "province") {
            fs.readFile("./data/file/city.txt", (err, data) => {
                if (data == "city") {
                    fs.readFile("./data/file/district.txt", (err, data) => {
                        if (data == "district") {
                            fs.readFile("./data/file/street.txt", (err, data) => {
                                if (data == "street") {
                                    fs.readFile("./data/file/adm.txt", (err, data) => {
                                        console.log("1:" + data.toString());
                                    });
                                }
                            });
                        }
                    });
                }
            });
        }
    });
    console.log("继续执行后续操作....");
}
fun1();
// Promise对象是处理异步操作回调函地狱的解决方案
function fun2() {
    //Promise对象,在创建时需要传递一个回调函数,
    //回调函数的第一个参数用于返回正确的结果,回调函数的第二个参数用于返回错误的结果
    new Promise((resolve, reject) => {
        //读取一个文件,并返回读取的结果
        fs.readFile("./data/file/province.txt", (err, data) => {
            //使用resolve返回的结果,在下一个then方法的回调函数中获取
            resolve(data.toString())
        })
    }).then(r => {
        return new Promise((resolve, reject) => {
            if (r == 'province') {
                fs.readFile("./data/file/city.txt", (err, data) => {
                    // 这里面的resolve返回的结果,可以在下一个then里面接收
                    resolve(data.toString())
                })
            }
        })
    }).then(r => {
        return new Promise((resolve, reject) => {
            if (r == 'city') {
                fs.readFile("./data/file/district.txt", (err, data) => {
                    resolve(data.toString())
                })
            }
        })
    }).then(r => {
        return new Promise((resolve, reject) => {
            if (r == 'district') {
                fs.readFile("./data/file/street.txt", (err, data) => {
                    resolve(data.toString())
                })
            }
        })
    }).then(r => {
        return new Promise((resolve, reject) => {
            if (r == 'street') {
                fs.readFile("./data/file/adm.txt", (err, data) => {
                    resolve(data.toString())
                })
            }
        })
    }).then(r => {
        console.log('2:' + r);
    })
}
fun2()
//自定义一个方法,用于读取文件
function myReadFile(path, option) {
    //方法返回一个Promise对象
    return new Promise((resolve, reject) => {
        fs.readFile(path, (err, data) => {
            if (data.toString() == option) {
                resolve(data.toString())
            }
        })
    })
}
myReadFile('./data/file/province.txt', 'province').then(r => {
    return myReadFile('./data/file/city.txt', 'city')
}).then(r => {
    return myReadFile('./data/file/district.txt', 'district')
}).then(r => {
    return myReadFile('./data/file/street.txt', 'street')
}).then(r => {
    return myReadFile('./data/file/adm.txt', 'adm')
}).then(r => {
    console.log('3:' + r);
})
// 定义方法时,添加async关键字,表示定义一个异步函数
async function fun3() {
    //then方法需要通过一个回调函数,接收上一个Promise的返回结果
    //通过await关键字,直接接收Promise的返回结果,不再需要写回调函数
    await myReadFile('./data/file/province.txt', 'province')
    await myReadFile('./data/file/city.txt', 'city')
    await myReadFile('./data/file/district.txt', 'district')
    await myReadFile('./data/file/street.txt', 'street')
    let r = await myReadFile('./data/file/adm.txt', 'adm')
    console.log('4:' + r);
}
fun3()

结果:

继续执行后续操作....
1:adm
3:adm
4:adm
2:adm

封装ajax方法 :

    <h2 id="province"></h2>
    <h2 id="city"></h2>
    <h2 id="district"></h2>
    <h2 id="street"></h2>
    <h2 id="adm"></h2>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
    <script>
        //原始写法
        function loadAddress() {
            $.get('./data/json/province.json', r => {
                if (r.name == 'province') {
                    $('#province').text(r.name)
                    $.get('./data/json/city.json', r => {
                        if (r.name == 'city') {
                            $('#city').text(r.name)
                            $.get('./data/json/district.json', r => {
                                if (r.name == 'district') {
                                    $('#district').text(r.name)
                                    $.get('./data/json/street.json', r => {
                                        if (r.name == 'street') {
                                            $('#street').text(r.name)
                                            $.get('./data/json/adm.json', r => {
                                                $('#adm').text(r.name)
                                            })
                                        }
                                    })
                                }
                            })
                        }
                    })
                }
            })
        }
        // loadAddress()

        //使用Promise封装
        function myGet(url, option) {
            return new Promise((resolve, reject) => {
                $.get(url, r => {
                    if (r.name == option) {
                        resolve(r)
                    }
                })
            })
        }
        function loadAddress2() {
            myGet('./data/json/province.json', 'province').then(r => {
                $('#province').text(r.name)
                return myGet('./data/json/city.json', 'city')
            }).then(r => {
                $('#city').text(r.name)
                return myGet('./data/json/district.json', 'district')
            }).then(r => {
                $('#district').text(r.name)
                return myGet('./data/json/street.json', 'street')
            }).then(r => {
                $('#street').text(r.name)
                return myGet('./data/json/adm.json', 'adm')
            }).then(r => {
                $('#adm').text(r.name)
            })
        }
        // loadAddress2()
        // 使用异步函数
        async function loadAddress3() {
            //使用await获取Promise对象的返回结果
            let p = await myGet('./data/json/province.json', 'province')
            $('#province').text(p.name)
            let c = await myGet('./data/json/city.json', 'city')
            $('#city').text(c.name)
            let d = await myGet('./data/json/district.json', 'district')
            $('#district').text(d.name)
            let s = await myGet('./data/json/street.json', 'street')
            $('#street').text(s.name)
            let r = await myGet('./data/json/adm.json', 'adm')
            $('#adm').text(r.name)
        }
        loadAddress3()
    </script>

 结果:

province

city

district

street

adm

promise的all() :

        let m1 = new Promise((resolve, reject) => {
            $.get('./data/json/province.json', r => {
                resolve(r)
            })
        })
        let m2 = new Promise((resolve, reject) => {
            $.get('./data/json/city.json', r => {
                resolve(r)
            })
        })
        let m3 = new Promise((resolve, reject) => {
            $.get('./data/json/district.json', r => {
                resolve(r)
            })
        })
        m1.then(r => {
            console.log(r);
        })
        m2.then(r => {
            console.log(r);
        })
        m3.then(r => {
            console.log(r);
        })

        //Promise.all()方法,用于将异步操作,按顺序接收
        Promise.all([m1, m2, m3]).then(r => {
            //注意:then的回调函数,返回的是每个Promise对象的返回结果。
            console.log(r);
        })

        console.log('继续执行后续代码...');

 结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值