- 使用ES6的模块化语法
- 使用Promise 解决回调地狱的问题
- 如何使用async/await简化Promise的调用
- 说出Event Loop
- 说出宏任务与微任务的执行顺序
第一章 ES6模块化与异步编程高级用法
1-1 回顾+模块化
回顾: node.js如何实行模块化
- node.js 遵循了CommonJS的模块化规范:(1)导入其他模块使用
require()
方法 - 模块对外共享成员使用
module.exports
对象
- node.js 遵循了CommonJS的模块化规范:(1)导入其他模块使用
模块化的好处 :
- 都遵循相同的代码规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用
前端模块化规范的分类:
ES6 模块化规范的定义:
- ES6模块化规范是浏览器与服务器端通用的模块化开发规范。它的出现极大降低了前端开发者的模块化学习成本
ES6模块规范定义:
- 每个JS文件都是一个独立的模块
- 导入其他模块成员使用
import
关键字 - 向外共享模块成员使用
export
关键字
在node.js中默认仅支持CommonJS模块化规范,若想基于node.js体验与学习ES6的模块化语法,可以按照如下两个步骤进行配置 :
- 确保安装v14.15.1或更高版本的node.js
- 在package.json 的根节点中添加“type”:“module”节点
ctrl + ~
——-> npm init -y : 快速初始化一个包管理配置文件(package.json)- 添加
"type": “module",
- 添加
1-3 导出与默认导入
- 默认导出 :
export default
默认导出的成员1
2
3
4
5
6
7let n1 = 10; //定义模块私有成员 n1
let n2 = 20;
function show(){} //定义模块私有方法 show
export default {
n1,show
} - 默认导入的语法 :
import 接受名称 form '模块块标识符';
1
2import m1 from './默认导入.js';
console.log(m1); //{ n1: 10, show: [Function: show] } - 默认导出与默认导入的注意事项
- 只能使用唯一的默认导出
export
- 默认导入时的接收名称可以是任意名称,只要合法的成员名称即可;
1-4 按需导入与按需导出
按需导出 :
export 按需导出的成员;
1
2
3export let s1 = 'aaa';
export let s2 = 'ccc';
export function say(){}按需导入 :
1
2
3
4import {s1,s2,say} from '模块标识符'
log(s1); //打印输出 aaa
log(say); //打印输出 [Function : say]注意事项 :
每个模块中可以多次按需导出
按需导入的成员名称 必须和按需导入的名称保持一致
按需导入时,可以使用as关键字进行重命名
-
import{s2 as atr2} from ''
- log(s2);
-
按需导入可以和默认导入一起使用(默认导入的不需要写在花括号中)
导入 :
1
2
3
4
5import info, { s1, say } from './02.按需导出.js'
console.log(s1)
console.log(say)
console.log(info);导出
1
2
3
4
5
6
7
8export let s1 = 'aaa';
export let s2 = 'ccc';
export function say() {};
export default { //默认导出
a: 20 //必须赋值才能导出
}
1-5 直接导入并执行模块中的代码
只是想单纯执行某个模块中的代码,并不需要得到模块中向外共享的成员。可以直接导入并执行模块代码
示例代码 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 当前文件模块为 03
for(let i =0; i < 3; i++){
log(i);
}
----------------------------------
//直接导入并执行模块代码,不需要得到模块向外共享的成员
import '03'
-----------结果-----
0
1
2
第二章 Promise
2-1 回调地狱及Promise的作用
多层回调函数的相互嵌套,就形成了回调地狱
- 代码耦合性太强,如果想要更改顺序,牵一发而动全身,难以维护
- 大量的冗杂代码相互嵌套,代码的可读性变差
例如 :
1
2
3
4
5
6
7
8
9
10setTimeout( () => { //第一层回调函数
log('延时1s输出');
setTimeout(() => { //第2层回调函数
log('在延时2秒输出')
setTimeout(()=> {
log("在延时3秒输出")
},3000)
},2000)
},1000)
2-2 Promis的基本概念
Promise是一个构造函数
- 可以创建Promise的 实例:
const p = new Promise();
- new 出来的 Promise 实例对象,代表一个异步操作
- 可以创建Promise的 实例:
Promise.prototype (构造函数的原型对象)上包含一个
.then()
方法- 每一次
new Promise()
构造函数得到的实例对象, - 都可以通过原型链的方式访问到.then()方法,例如 :p.then()
- 每一次
.then() 方法用来预先指定成功和失败的回调函数
- p.then(成功的回调函数, 失败的回调函数)
- p.then(result => {}, error =>{})
- 调用.then() 方法时,成功的回调函数是必选的,失败的回调函数是可选的
2-3 基于回调函数按顺序读取文件内容&then-fs的基本使用
提出问题 : 如何解决如下的回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16fs.readFile('./files/1.txt','utf8',(err1,r1) => {
if(err1) return log(err1,message) //读取文件1 失败
log (r1); // 读取 文件 1 成功
// 读取文件 2.txt
fs.readFile('./files/2.txt','utf8',(err2,r2) => {
if(err2) return log(err2.message) //读取文件 2 失败
log(r2) //读取文件 2 失败
// 读取文件 3.txt
fs.readFile('./files/3.txt','utf8',(err3,r3) => {
if(err3) return log(err3,message) //读取文件 3 失败
log(r3) //读取文件 3 成功
})
})
})由于node.js官方提供的fs模块仅支持以回调函数等方式读取文件,不支持Promise的调用方式
- 因此,需要先运行如下的命令,安装
then-fs
这个第三方包,从而支持我们基于Promise的方式读取文件的内容 npm install then-fs
- 因此,需要先运行如下的命令,安装
调用then-fs 提供的read File()方法,可以异步读取文件的内容,它的返回值是Promise的实例对象。
可以调用
.then()方法
为每个Promise异步操作指定成功和*失败之后的回调函数。示例代码:
1
2
3
4
5
6
7
8/**
*基于 Promise 的方式读取文件
*/
import thenFs from 'then-fs'
thenFs.readFile('./files/1.txt', 'utf8').then((r1) => { console.log(r1) })
thenFs.readFile('./files/2.txt', 'utf8').then((r2) => { console.log(r2) })
thenFs.readFile('./files/3.txt', 'utf8').then((r3) => { console.log(r3) })
2-4 .then()方法以及基于Promise 按顺序读取文件的内容 &通过catch方法捕获错误
如果上一个.then()方法返回了一个新的Promise实例对象,则可以通过下一个.then()继续进行处理。
- 通过.then()方法的链式调用 , 就解决了回调地狱的问题
```js
import thenFs from ‘then-fs’thenFs
.readFile('./files/1.txt', 'utf8') .then((r1) => { console.log(r1) return thenFs.readFile('./files/2.txt', 'utf8') }) .then((r2) => { console.log(r2); return thenFs.readFile('./files/3.txt', 'utf8') }) .then((r3) => { console.log(r3); })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
3. 通过.catch方法捕获错误
- 在Promise的链式操作如果发生了错误,可以使用`Promise.prototype.catch`方法进行捕获和处理 :
```js
import thenFs from 'then-fs'
thenFs
.readFile('./files/11.txt', 'utf8')
.then((r1) => {
console.log(r1)
return thenFs.readFile('./files/2.txt', 'utf8')
})
.then((r2) => {
console.log(r2);
return thenFs.readFile('./files/3.txt', 'utf8')
})
.then((r3) => {
console.log(r3);
})
.catch((err) => {
console.log(err.message);
})如果不希望前面的错误导致后续的.then无法正常执行,则可以将.catch 的调用提前,示例 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import thenFs from 'then-fs'
thenFs
.readFile('./files/11.txt', 'utf8')
.catch((err) => {
console.log(err.message);
})
.then((r1) => {
console.log(r1)
return thenFs.readFile('./files/2.txt', 'utf8')
})
.then((r2) => {
console.log(r2);
return thenFs.readFile('./files/3.txt', 'utf8')
})
.then((r3) => {
console.log(r3);
})
2-5 Promise —- Promise.all()以及Promise.race()方法
- Promise.all()方法会发起并行的Promise异步操作, 等所有的异步操作全部结束后才会执行下一步的.then 操作(等待机制)
- 示例代码 :
1 | import thenFs from 'then-fs' |
Promise.race()方法会发起并行的Promise异步操作,只要任何一个异步操作完成,就立刻执行下一步的.then操作(赛跑机制)
实例代码 :
1
2
3
4
5
6
7
8
9
10
11
12
13import thenFs from 'then-fs'
const promiseArr = [
thenFs.readFile('./files/1.txt', 'utf8'),
thenFs.readFile('./files/2.txt', 'utf8'),
thenFs.readFile('./files/3.txt', 'utf8'),
]
Promise.race(promiseArr).then(result => {
console.log(result);
})
//运行结果
222
2-6 基于Promise封装读文件的方法
方法的封装要求 :
- 方法的名称要定义为getFile
- 方法接收一个形参fpath,表示要读取的文件的路径
- 方法的返回值为Promise实例对象
getFile方法的基本定义 :
1
创建具体的异步操作
则需要在new Promise() 构造函数期间, 传递一个function函数,将具体的异步操作定义到function函数内部
示例代码 :
1
2
3
4
5
6
7
8
9// 1. 方法的名称为 getFile
// 2. 方法接收一个形参 fpath,表示要读取的文件的路径
function getFile(fpath) {
//3. 方法的返回值为Promise实例对象
return new Promise(function() { //注意: 该行代码中的new Promise()只是创建了一个形式上的异步操作(不知道是读文件的还是ajax的)
// 4. 下面这行代码,表示这是一个具体的、读文件的异步操作
fs.readFile(fpath, 'utf8', (err, dataStr) => {})
})
}
2-7 async /await
什么是async / await
async / await 是ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作
在async / await 出现之前, 开发者只能通过链式 .then() 的方式 处理Promise 异步操作、
上面有示例,.then链式的优缺点 :
- 优点: 解决了回调地狱的问题
- 缺点 : 代码冗杂、阅读性差、不易理解
示例:
1
2
3
4
5
6
7
8
9
10
11
12import thenFs from 'then-fs'
async function getAllFile() {
const r1 = await thenFs.readFile('./files/1.txt', 'utf8');
console.log(r1);
const r2 = await thenFs.readFile('./files/2.txt', 'utf8')
console.log(r2);
const r3 = await thenFs.readFile('./files/3.txt', 'utf8')
console.log(r3);
}
getAllFile()async / await 的使用事项
如果在function中使用了await ,则function 必须被 async 修饰
在async 方法中, 第一个await之前的代码会同步执行, await之后的代码会异步修饰
import thenFs from 'then-fs' console.log('A'); async function getAllFile() { console.log('B '); const r1 = await thenFs.readFile('./files/1.txt', 'utf8'); console.log(r1); const r2 = await thenFs.readFile('./files/2.txt', 'utf8') console.log(r2); const r3 = await thenFs.readFile('./files/3.txt', 'utf8') console.log(r3); console.log('D'); } getAllFile() console.log('C');
运行结果:
1
2
3
4
5
6
7
8A
B
C
1111
222
333
D
2-8 EventLoop
JavaScript是单线程的语言
- JavaScript 是一门单线程执行的编程语言,也就是说,同一时间只能做一件事
- 单线程执行任务队列的问题 :
- 如果前一个任务非常耗时,则后续的任务不得不一直等待,从而导致程序假死的问题
同步任务和异步任务 :
- JavaScript 把待执行的任务分为两类:(同步任务和异步任务)
- 同步任务 (synchronous) :
- 又叫非耗时任务,指的是在主线程上排队执行的那些任务
- 只有前一个任务执行完毕,才能执行后一个任务
- 异步任务(asynchronous)
又叫做耗时任务,异步任务由JavaScript 委托给宿主环境进行执行
当异步任务执行完成后,会通知JavaScript主线程执行异步任务的回调函数
EventLoop的基本概念
- JavaScript主线程从“任务队列”中读物异步任务的回调函数,放到执行栈中依次执行
- 这个过过程循环不断,所以整个的这种运行机制又称为 EventLoop(事件循环)
结合 EventLoop 分析输出的顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14import thenFs from 'then-fs'
log('A')
thenFs.readFile('./files/1.txt','utf8').then(dataStr => {
log('B')
})
setTimeout(() => {
log('C')
},0)
log('D')
//运行结果
ADCB- A和D属于 同步任务,会根据代码的先后顺序依次被执行
- C和B属于异步任务,他们的回调函数会被加入到任务队列中,等待主线程空闲时在执行
第三章 宏任务和微任务 & API
3-1 宏任务和微任务
JavaScript 把异步任务又做了进一步的划分, 异步任务又分为两类
- 宏任务 (macrotask):
- 异步Ajax请求
- setTimeout , setInterval
- 文件操作
- 其它宏任务
- 微任务(microtask)
- Promise.then / .catch / .finally
- process.nextTick
- 其他微任务
- 宏任务 (macrotask):
宏任务和微任务的执行顺序
- 每一个宏任务执行完之后,都会检查是否存在待执行的微任务
- 如果有,则执行所有微任务之后,再继续执行下一个宏任务
取银行办业务的场景
- 小云和小腾去银行办业务,首先要取号后进行排队
- 宏任务队列
- 假设当前银行网点只有一个柜员,小云在办理业务时,小腾只能等待
- 单线程,宏任务按次序执行
- 小云办完存款业务后,柜员询问是否还想办理其他业务?
- 当前宏任务执行完毕,检查是否有微任务
- 小云告诉柜员 : 想买理财产品
- 执行微任务,后续宏任务被推迟
- 小云离开柜台后,柜员开始为腾办理业务
- 所有微任务执行完毕,开始执行下一个宏任务
- 小云和小腾去银行办业务,首先要取号后进行排队
请分析以下代码输出的顺序
- 156234789
3-2 API接口案例 — 初始化项目
案例需求
- 基于MySQL数据库 + Express 对外提供 用户列表的API接口服务
- 用到的技术点 :
- 第三方包 exxpress 和mysql2
- ES6 模块化
- Promise
- async / await
主要的实现步骤、
- 搭建项目的基本结构
- 创建基本的服务器
- 创建 db 数据库操作模块
- 创建 user_ctrl 业务模块
- 创建user_router 路由模块
搭建项目的基本结构
- 启用ES6 模块化支持
- 在package.json中声明
"type":"module"
- 在package.json中声明
- 安装第三方依赖包
- 运行
npm install express@4.17.1 mysql2@2.2.5
- 运行
- 启用ES6 模块化支持
创建基本的服务器
1
2
3
4
5
6
7//使用ES6 的默认导入语法
import express from 'express'
const app = express()
app.listen(80, () => {
console.log('server running at http://127.0.0.1')
})
3-3 创建db数据库操作模块
3-4 创建并挂载路由模块(使用user_router路由模块)
- 有个小bug,等之后