1.Node是什么
Node是一个基于Chrome V8引擎的JavaScript代码运行环境。
1.1 运行环境
浏览器(软件)能够运行JavaScript代码,浏览器就是JavaScript代码的运行环境
Node(软件)能够运行JavaScript代码,Node就是JavaScript代码的运行环境
1.2 Node.js 的组成
JavaScript 由三部分组成,ECMAScript,DOM,BOM
Node.js是由ECMAScript及Node 环境提供的一些附加API组成的,包括文件、网络、路径等等一些更加强大的 API
1.3 Node.js基础语法
所有ECMAScript语法在Node环境中都可以使用。
在Node环境下执行代码,使用Node命令执行后缀为.js的文件即可
1.4 Node.js全局对象global
在浏览器中全局对象是window,在Node中全局对象是global
Node中全局对象下有以下方法,可以在任何地方使用,global可以省略
console.log() 在控制台中输出
setTimeout() 设置超时定时器
clearTimeout() 清除超时时定时器
setInterval() 设置间歇定时器
clearInterval() 清除间歇定时器
2.Node.js模块化开发
2.1 JavaScript开发弊端
JavaScript在使用时存在两大问题,文件依赖和命名冲突
2.2 软件中的模块化开发
一个功能就是一个模块,多个模块可以组成完整应用,抽离一个模块不会影响其他功能的运行
2.3 Node.js中模块化开发规范
Node.js规定一个JavaScript文件就是一个模块,模块内部定义的变量和函数默认情况下在外部无法得到
模块内部可以使用exports对象进行成员导出, 使用require方法导入其他模块
2.3.1 模块成员导出
// a.js
// 在模块内部定义变量
let version = 1.0;
// 在模块内部定义方法
const sayHi = name => `您好, ${name}`;
// 向模块外部导出数据
exports.version = version;
exports.sayHi = sayHi;
//export.属性名 = 已存在的变量名
2.3.2 模块成员的导入
// b.js
// 在b.js模块中导入模块a
let a = require('./b.js');
// let a = require('./b');
// 输出b模块中的version变量
console.log(a.version);
// 调用b模块中的sayHi方法 并输出其返回值
console.log(a.sayHi('黑马讲师'));
导入模块时后缀可以省略
2.3.3 模块成员导出的另一种方式
module.exports.version = version;
module.exports.sayHi = sayHi;
exports是module.exports的别名(地址引用关系),导出对象最终以module.exports为准
//b.js
const add = (n1,n2) => n1+n2;
const x = 100;
exports.x = x;
module.exports.add = add;
//demo.js
const a = require('./b.js');
console.log(a);
//{ x: 100, add: [Function: add] }
当exports和module.exports指向不同对象时,导出对象最终以module.exports为准
//b.js
const add = (n1,n2) => n1+n2;
module.exports.add = add;
module.exports = {
name: 'zhangsan'
}
const x = 100;
exports.x = x;
//demo.js
const a = require('./b.js');
console.log(a);
//{ name: 'zhangsan' }
3. 系统模块
3.1 什么是系统模块
Node运行环境提供的API. 因为这些API都是以模块化的方式进行开发的, 所以我们又称Node运行环境提供的API为系统模块
3.2 系统模块fs 文件操作
f:file 文件 ,s:system 系统,文件操作系统。
const fs = require('fs');
-
读取文件内容
fs.reaFile('文件路径/文件名称'[,'文件编码'], callback);
因为读取需要耗时,所以读取结果需要用参数传递,不能通过readFile直接返回
//b.js const fs = require('fs'); fs.readFile('./demo.js', 'utf-8', (err, doc) => { // 如果文件读取错误 err是一个对象 包含错误信息 // 如果文件读取正确 err是null // doc是文件读取的结果 console.log(err); console.log(doc); })
-
写入文件内容
fs.writeFile('文件路径/文件名称', '数据', callback);
const fs = require('fs'); fs.writeFile('./demo.txt', '即将写入的内容', err => { if (err != null) { console.log(err); return; } console.log('文件写入成功'); })
3.3 系统模块path 路径操作
为什么要进行路径拼接
- 不同操作系统的路径分隔符不统一
- /public/uploads/avatar
- Windows 上是 \ /
- Linux 上是 /
3.4 路径拼接语法
path.join('路径', '路径', ...)
//返回路径结果
// 导入path模块
const path = require('path');
// 路径拼接
let finialPath = path.join('itcast', 'a', 'b', 'c.css');
// 输出结果 itcast\a\b\c.css
console.log(finialPath);
3.5 相对路径VS绝对路径
- 大多数情况下使用绝对路径,因为相对路径有时候相对的是命令行工具的当前工作目录
- 在读取文件或者设置文件路径时都会选择绝对路径
- 使用__dirname获取当前文件所在的绝对路径
//b.js
const fs = require('fs');
const path = require('path');
console.log(path.join(__dirname, 'demo.js'));
fs.readFile(path.join(__dirname, 'demo.js'), 'utf-8', (err, doc) => {
console.log(err);
console.log(doc);
})
//cmd
F:\document\学习教程\笔记\9.nodejs> node b.js
//输出
F:\document\学习教程\笔记\9.nodejs\demo.js
null
const a = require('./b');
console.log(a);
//cmd上一级路径
F:\document\学习教程\笔记\9.nodejs> cd ..
F:\document\学习教程\笔记> node .\9.nodejs\b.js
//输出
F:\document\学习教程\笔记\9.nodejs\demo.js
null
const a = require('./b');
console.log(a);
4. 第三方模块
4.1 什么是第三方模块
别人写好的、具有特定功能的、我们能直接使用的模块即第三方模块,由于第三方模块通常都是由多个文件组成,并且被放置在一个文件夹中,所以又名包
第三方模块有两种存在形式:
- 以js文件的形式存在,提供实现项目具体功能的API接口
- 以命令行工具形式存在,辅助项目开发
4.2 获取第三方模块
npmjs.com:第三方模块的存储和分发仓库
npm (node package manager) : node的第三方模块管理工具
- 下载:npm install 模块名称
- 卸载:npm unintall package 模块名称
全局安装与本地安装:
- 命令行工具:全局安装(所有项目可以使用到)
- 库文件:本地安装(当前项目可以使用到)
4.3 第三方模块 nodemon
nodemon是一个命令行工具,用以辅助项目开发。
在Node.js中,每次修改文件都要在命令行工具中重新执行该文件,非常繁琐
使用步骤:
- 使用npm install nodemon –g 下载它
- 在命令行工具中,用nodemon命令替代node命令来执行文件(nodemon 文件名)
4.4 第三方模块 nrm
nrm ( npm registry manager ):npm下载地址切换工具
npm默认的下载地址在国外,国内下载速度慢
使用步骤:
- 下载nrm:npm install nrm –g
- 查询国内可用下载地址列表:nrm ls
- 切换npm下载地址:nrm use 下载地址名称(如nrm taobao)
- 之后就可以安装需要的(如npm install gulp)
4.5 Glup
gulp是一个自动化构建工具,基于node平台开发的前端构建工具,主要用来设定程序自动处理静态资源的工作。简单的说,gulp就是用来打包项目的
4.6 Glup能做什么
- 项目上线、HTML CSS JS文件压缩合并
- 语法转换(es6 less...)
- 公共文件抽离
- 修改文件浏览器自动刷新
4.7 Glup使用
- 下载gulp库文件 npm install gulp
- 项目根目录新建gulpfile.js文件
- 重构项目文件夹结构 src目录存放源代码文件 dist目录存放构建后文件
- 在gulpfile.js文件中编写任务
- 在命令行工具中执行gulp任务
4.8 Gulp提供的方法
- gulp.src()获取任务要处理的文件
- gulp.dest()输出文件
- gulp.task()建立gulp任务
- gulp.watch()监控文件的变化
const gulp = require('gulp');
gulp.task('first', () => {
console.log('第一个gulp任务执行了');
gulp.src('./src/css/base.css')
.pipe(gulp.dest('./dist/css'));
});
//如果报错Did you forget to signal async completion?
const gulp = require('gulp');
gulp.task('first', async() => {
await console.log('第一个gulp任务执行了');
gulp.src('./src/css/base.css')
.pipe(gulp.dest('./dist/css'));
});
4.9 gulp插件
- gulp-htmlmin html文件压缩
- gulp-csso 压缩css
- gulp-babel JavaScript语法转化
- gulp-less less语法转化
- gulp-uglify 压缩混淆js
- gulp-file-include 公共文件包含
- browsersync 浏览器实时同步
使用插件步骤:
(查看文档apihttps://www.npmjs.com/package/gulp-htmlmin)
- 下载npm install
- 引入
- 调用
// html任务
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');
gulp.task('htmlmin', async() => {
await gulp.src('./src/*.html')
// 1.抽取html公共代码
.pipe(fileinclude())
// 2.html代码压缩
.pipe(htmlmin({ collapseWhitespace: true }))
.pipe(gulp.dest('dist'));
});
// 3.新建公共html文件: 将index.html头部和detail.html头部(公共)剪切到src新建common文件中的header.html
// 4.引入公共文件: 在index.html头部和detail.html头部原来位置写入@@include('./common/header.html')
// 5.执行gulp htmlmin
// css任务
const gulp = require('gulp');
const csso = require('gulp-csso');
const less = require('gulp-less');
gulp.task('cssmin', async()=> {
await gulp.src(['./src/css/*.less', './src/css/*.css'])
// 1.less语法转换
.pipe(less())
// 2.css代码压缩
.pipe(csso())
// 输出结果
.pipe(gulp.dest('dist/css'));
});
// js任务
// 1.es6代码转换
// 2.代码压缩
gulp.task('jsmin', async()=>{
await gulp.src('./src/js/*.js')
// 可以判断当前代码的运行环境,将代码转换为当前环境所支持的代码
.pipe(babel({
presets: ['@babel/env']
}))
.pipe(uglify())
.pipe(gulp.dest('dist/js'))
});
// 复制文件夹
gulp.task('copy', async() => {
await gulp.src('./src/img/*')
.pipe(gulp.dest('dist/img'));
gulp.src('./src/upload/*')
.pipe(gulp.dest('dist/upload'));
})
// 构建任务gulp 4
// 执行gulp或gulp default后 会依次执行后面的命令
gulp.task('default', gulp.series('htmlmin', 'cssmin', 'jsmin', 'copy'));
5.package.json文件
5.1 node_mudules文件夹问题
- 文件夹太大,项目拷贝给其他人时,传递速度太慢
- 模块依赖关系需要被记录,确保模块版本和当前一致,否则项目运行报错
5.2 package.json文件作用
项目描述文件,记录了当前项目信息,如项目名称,版本,作者,github地址,依赖的第三方模块等,使用npm init -y命令生成(y即yes 按默认)
{
"name": "gulp-demo",
"version": "1.0.0",
"description": "",
"main": "gulpfile.js",
"dependencies": {
"gulp-babel": "^8.0.0",
"gulp-csso": "^4.0.1",
"gulp-htmlmin": "^5.0.1",
"gulp-less": "^4.0.1",
"gulp-uglify": "^3.0.2",
"gulp-file-include": "^2.2.2"
},
"devDependencies": {
"gulp": "^4.0.2"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"build": "nodemon app.js"
},
"keywords": [],
"author": "",
"license": "ISC"
}
字段含义:
- "license": "ISC" :开放源代码协议
- scripts:存放命令的别名(npm run +别名,如npm run build)
- main主模块
此时,package.json在项目根目录下,根目录下 直接安装项目依赖和开发依赖的命令
npm install
如果开发环境下,只想安装项目依赖则
npm install --production
5.3 项目依赖
在项目的开发阶段和线上运营阶段,都需要依赖的第三方包,称为项目依赖
npm install/i xxx@version -S/--save或者npm install
下载的文件默认添加到package.json的dependencies字段
5.4 开发依赖
devDenpendencies是开发依赖,是开发阶段需要依赖,线上生产环境上并不需要依赖的第三方包
npm install/i xxx -D/--save-dev
如 npm install gulp --save-dev
如:构建工具可以归为开发依赖,它是为了生成生产环境的代码,在线上使用的代码其实是他们工作的结果,也就是说在线上时,并不需要他们。
5.5 package-lock.json文件的作用
- 锁定包的版本,确保下次下载
- 加载下载速度,因为记录依赖的第三方包树状结构和下载地址
6.Node.js模块查找规则
6.1 模块有路径无后缀时
require('./find.js');//直接查找
require('./find');
- 在当前路径下查找同名js文件
- 没找到,再看是否有同名文件夹
- 如果是,则找文件夹下的index.js
- 没有index.js,则在当前文件夹中package.json找main选项的入口文件
- 入口文件没找到,报错
6.2 模块无路径无后缀时
require('find');
- Node.js会假设它是系统模块,到node_mudules文件夹查找同名js文件
- 没找到,再看是否有同名文件夹
- 如果是,则找文件夹下的index.js
- 没有index.js,则在该文件夹中package.json找main选项的入口文件
- 没找到,报错
7.请求响应原理及HTTP协议
7.1 服务器端基础概念
7.1.1 网站的组成
网站应用程序主要分为两大部分:客户端和服务器端
客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用HTML、CSS、JavaScript构建
服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑
7.1.2 Node网站服务器
能够提供网站访问服务的机器就是网站服务器,它能够接收客户端的请求,能够对请求做出响应,一般通过远程客户端控制它
那如何访问对应的网站服务器?引入ip地址
7.1.3 IP地址
互联网中设备的唯一标识
IP是Internet Protocol Address的简写,代表互联网协议地址
7.1.4 域名
由于IP地址难于记忆,所以产生了域名的概念,所谓域名就是平时上网所使用的网址。
http://www.itheima.com => http://124.165.219.100/
虽然在地址栏中输入的是网址, 但是最终还是会将域名转换为ip才能访问到指定的网站服务器
7.1.5 端口
端口用来区分服务器电脑中提供的不同的服务,是计算机与外界通讯交流的出口。
7.1.6 URL
统一资源定位符,又叫URL(Uniform Resource Locator),是专为标识Internet网上资源位置而设的一种编址方式,我们平时所说的网页地址指的即是URL
URL的组成
- 传输协议://服务器IP或域名:端口/资源所在位置标识
- http://www.itcast.cn/news/20181018/09152238514.html
- http:超文本传输协议,提供了一种发布和接收HTML页面的方法
7.1.7 开发过程中客户端和服务器端说明
在开发阶段,客户端和服务器端使用同一台电脑,即开发人员电脑
客户端(浏览器) + 服务器端(Node)
本地域名localhost 本地IP:127.0.0.1
7.2 创建web服务器
// 1.引用系统模块
const http = require('http');
// 2.创建web服务器
const app = http.createServer();
// 3.当客户端发送请求的时候
app.on('request', (req, res) => {
// 4. 响应
res.end('hi, user
');
});
// 5.监听3000端口
app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问 localhost:3000')
// 6.localhost:3000访问网站 完成基本的请求和响应过程
7.3 HTTP协议
7.3.1 HTTP协议的概念
超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)规定了如何从网站服务器传输超文本到本地浏览器,它基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准
7.3.2 报文
在HTTP请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式
7.3.3 请求报文
报文信息可以在浏览器中Network查看
- 请求方式 (Request Method)
- GET 请求数据
- POST 发送数据
- 请求地址 (Request URL)
app.on('request', (req, res) => {
req.headers // 获取请求报文
req.url // 获取请求地址
req.method // 获取请求方法
});
输入地址回车,默认请求方式为get
const http = require('http');
const app = http.createServer();
app.on('request', (req, res) => {
// 获取请求报文
console.log(req.headers);
// 获取报文中某个属性值
console.log(req.headers['accept']);
// 根据请求地址不同 响应不同
if (req.url == "/index" || req.url == "/") {
res.end("首页内容");
} else if (req.url == '/login') {
res.end('登录页面内容');
}
});
app.listen(3000);
console.log("3000监听中");
7.3.4 响应报文
- HTTP状态码
- 200 请求成功
- 404 请求的资源没有被找到
- 500 服务器端错误
- 400 客户端请求有语法错误
- 内容类型
- text/html
- text/css
- application/javascript
- image/jpeg
- application/json
app.on('request', (req, res) => {
// 设置响应报文
res.writeHead(200, {
'Content-Type': 'text/html;charset=utf8'
});
if (req.url == "/index" || req.url == "/") {
res.end("首页内容
");
}
});
7.4 HTTP请求与响应处理
7.4.1 请求参数
客户端向服务器端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数的形式传递到服务器端,比如登录操作
7.4.2 GET请求参数的获取
-
参数被放置在浏览器地址栏
例如:http://localhost:3000/?name=zhangsan&age=20
参数获取需要借助系统模块url,url模块用来处理url地址
const http = require('http');
// 导入url系统模块 用于处理url地址
const url = require('url');
const app = http.createServer();
app.on('request', (req, res) => {
// 第一个参数不写 默认200
res.writeHead(200, {
'Content-Type': 'text/html;charset=utf8'
});
// 将url路径的各个部分解析出来并返回对象
// true 代表将参数解析为对象格式
let {query, pathname} = url.parse(req.url,true);
console.log(query.name);
console.log(query.age);
if (pathname == "/index" || pathname == "/") {
res.end("首页内容
");
} else if (pathname == '/login') {
res.end('登录页面内容');
}
});
app.listen(3000);
7.4.3 POST请求参数的获取
参数被放置在请求体中进行传输
-
POST参数是通过data事件和end事件接收的
参数传递时触发data事件,传递完成时触发end事件
使用querystring系统模块将参数转换为对象格式
const http = require('http');
const queryString = require('querystring');
const app = http.createServer();
app.on('request', (req, res) => {
let postData = '';
req.on('data', param => {
postData += param;
})
req.on('end', () => {
console.log(queryString.parse(postData));
//uname为input的name属性值
console.log(queryString.parse(postData).uname);
})
res.end('ok');
});
app.listen(3000);
console.log("3000监听中");
7.4.4 路由
http://localhost:3000/index
http://localhost:3000/login
路由是指客户端请求地址与服务器端程序代码的对应关系。简单的说,就是请求什么响应什么
const http = require('http');
// 导入url系统模块 用于处理url地址
const url = require('url');
const app = http.createServer();
app.on('request', (req, res) => {
const method = req.method.toLowerCase();
let {pathname} = url.parse(req.url);
res.writeHead(200, {
'content-type': 'text/html;charset=utf-8'
})
if (method == 'get') {
if (pathname == "/index" || pathname == "/") {
res.end("首页内容
");
} else if (pathname == '/login') {
res.end('登录页面内容');
}
}
});
app.listen(3000);
第三方模块 router
- 功能:实现路由
- 使用步骤:
const getRouter = require('router')
//1. 获取路由对象
const router = getRouter();
//2. 调用路由对象提供的方法创建路由
router.get('/add', (req, res) => {
res.end('Hello World!')
})
app.on('request', (req, res) => {
//3.启用路由,使路由生效
router(req, res)
})
7.4.5 静态资源
服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如CSS、JavaScript、image文件
http://www.itcast.cn/images/logo.png
第三方模块 serve-static
- 功能:实现静态资源访问服务
- 步骤:
//1.引入serve-static模块,获取创建静态资源服务功能的方法
const serveStatic = require('serve-static')
//2. 调用方法创建静态资源服务,并指定静态资源目录
const serve = serveStatic('public')
app.on('request', () => {
//3. 启用静态资源服务功能
serve(req, res)
})
app.listen(3000)
7.4.6 动态资源
相同的请求地址不同的响应资源,这种资源就是动态资源
http://www.itcast.cn/article?id=1
http://www.itcast.cn/article?id=2
实例:根据请求地址,返回响应页面
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const app = http.createServer();
app.on('request', (req, res) => {
let { pathname } = url.parse(req.url);
pathname = pathname == '/' ? '/index.html' : pathname;
// 浏览器的url在项目中的路径 同目录下src文件存放着项目文件
let filePath = path.join(__dirname, 'src' + pathname);
// 请求文件的类型 html css js...
let type = mime.getType(filePath);
// 响应的内容类型
res.writeHead(200, {
'content-type': type
})
// 查找文件并响应
fs.readFile(filePath, (err, doc) => {
if (err != null) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf-8'
})
res.end('文件读取失败');
return;
} else {
res.end(doc);
}
})
});
app.listen(3000);
7.4.7 客户端请求途径
- GET方式
浏览器地址栏
link标签的href属性
script标签的src属性
img标签的src属性
-
Form表单提交
- POST方式
- Form表单提交
8. Node.js异步编程
8.1 同步API, 异步API
// 路径拼接 返回值获取结果
const public = path.join(__dirname, 'public');
// 请求地址解析 返回值获取结果
const urlObj = url.parse(req.url);
// 读取文件 函数参数获取结果
fs.readFile('./demo.txt', 'utf8', (err, result) => {
console.log(result);
});
同步API:只有当前API执行完成后,才能继续执行下一个API
console.log('before');
console.log('after');
异步API:当前API的执行不会阻塞后续代码的执行
console.log('before');
setTimeout(
() => { console.log('last');
}, 2000);
console.log('after');
8.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);
// 默认返回undefined
}
const msg = getMsg ();//undefined
8.3 回调函数
自己定义函数 让别人去调用
// getData函数定义
function getData (callback) {}
// getData函数调用
getData (() => {});
8.4 使用回调函数获取异步API执行结果
function getMsg (callback) {
setTimeout(function() {
callback({msg: 'hello node.js'})
}, 3000)
}
getMsg(function(data) {
console.log(data);
});//通过回调函数来传递结果{msg: 'hello node.js'}
8.5 同步API, 异步API的区别(代码执行顺序)
同步API从上到下依次执行,前面代码会阻塞后面代码的执行//sync
for (var i = 0; i < 100000; i++) {
console.log(i);
}
console.log('for循环后面的代码');
异步API不会等待API执行完成后再向下执行代码//async
console.log('代码开始执行');
setTimeout(() => { console.log('2秒后执行的代码')}, 2000);
setTimeout(() => { console.log('"0秒"后执行的代码')}, 0);
console.log('代码结束执行');
8.6 代码执行顺序分析
console.log('代码开始执行');
setTimeout(() => {
console.log('2秒后执行的代码');
}, 2000);
setTimeout(() => {
console.log('"0秒"后执行的代码');
}, 0);
console.log('代码结束执行');
同步代码执行区
- console.log('代码开始执行'); 执行
异步代码执行区
- setTimeout(callback1, 2000); 存放
- setTimeout(callback2, 0); 存放
回调函数队列
- callback1 = () => { console.log(2秒后执行的代码)} 存放
- callback2 = () => { console.log(0秒后执行的代码)} 存放
同步代码执行区
console.log('代码开始执行');
console.log('代码结束执行'); 执行
异步代码执行区
- setTimeout(callback1, 2000); 执行
- setTimeout(callback2, 0); 执行
回调函数队列
- callback1 = () => { console.log(2秒后执行的代码)}
- callback2 = () => { console.log(0秒后执行的代码)} 计时到,取出
同步代码执行区
console.log('代码开始执行');
console.log('代码结束执行');
callback2 = () => { console.log(0秒后执行的代码)} 存放,并执行
回调函数队列
- callback1 = () => { console.log(2秒后执行的代码)} 计时到,取出
同步代码执行区(执行结果)
console.log('代码开始执行');
console.log('代码结束执行');
callback2 = () => { console.log(0秒后执行的代码)}
callback1 = () => { console.log(2秒后执行的代码)} 存放,并执行
8.7 Node.js中的异步API
fs.readFile('./demo.txt', (err, result) => {});//文件读取
var server = http.createServer();
server.on('request', (req, res) => {});//事件监听
如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题要怎么解决呢?
需求:依次读取A文件、B文件、C文件
//回调地狱,一层嵌套一层,不利于代码维护
const fs = require('fs');
fs.readFile('./1.txt', 'utf-8', (err,result1) => {
console.log(result1);
fs.readFile('./2.txt', 'utf-8', (err,result2) => {
console.log(result2);
fs.readFile('./3.txt', 'utf-8', (err,result3) => {
console.log(result3);
});
});
});
//输出 1 2 3
8.8 Promise
Promise出现的目的是解决Node.js异步编程中回调地狱的问题
Promise是一个构造函数,将一个回调函数作为参数,回调函数2个参数为resolve和reject函数,实际是分别执行promise中的then和catch里面的回调函数,另外promise支持链式编程,可以在then后继续执行catch
resolve方法 将异步api执行结果传出去
reject方法 将失败的结果传出去
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); // 失败了)
需求:依次读取A文件、B文件、C文件 用promise解决嵌套地狱
function p1() {
return new Promise((resolve, reject) => {
fs.readFile('./1.txt', 'utf-8', (err, res) => {
resolve(res);
});
})
}
function p2() {
return new Promise((resolve, reject) => {
fs.readFile('./2.txt', 'utf-8', (err, res) => {
resolve(res);
});
})
}
function p3() {
return new Promise((resolve, reject) => {
fs.readFile('./3.txt', 'utf-8', (err, res) => {
resolve(res);
});
})
}
p1().then(res1 => {
console.log(res1);
return p2();
})
.then(res2 => {
console.log(res2);
return p3();
})
.then(res3 => {
console.log(res3);
});// 1 2 3
但是代码臃肿 可以通过异步函数解决
8.9 异步函数
异步函数可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了
const fn = async () => {};
async function fn () {}
async关键字
- 普通函数定义前加async关键字 普通函数变成异步函数
- 异步函数默认返回promise对象
- 在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法
- 在异步函数内部使用throw关键字抛出程序异常,之后的代码不执行
- 调用异步函数再链式调用then方法获取异步函数执行结果
- 调用异步函数再链式调用catch方法获取异步函数执行的错误信息
await关键字
- await关键字只能出现在异步函数中
- await promise await后面只能写promise对象 写其他类型的API是不不可以的
- await关键字可是暂停异步函数向下执行 直到promise返回结果
基础语法
async function p1() {
return 'p1';
}
async function p2() {
return 'p2';
}
async function p3() {
return 'p3';
}
async function run() {
let r1 = await p1();
let r2 = await p2();
let r3 = await p3();
console.log(r1);
console.log(r2);
console.log(r3);
}
run();
promisify方法的引入:使得异步函数api 可以使用await等异步函数语法
const fs=require('fs');
// 改造现有异步函数api 让其返回promise对象 从而支持异步函数语法
const promisify = require('util').promisify;
// 调用promisify方法改造现有异步api 让其返回promise对象
const readFile = promisify(fs.readFile);
async function run() {
let r1 = await readFile('./1.txt','utf-8');
let r2 = await readFile('./2.txt','utf-8');
let r3 = await readFile('./3.txt','utf-8');
console.log(r1);
console.log(r2);
console.log(r3);
}
run();// 1 2 3