# Es6 模块化和异步编程
# nodejs 如何实现模块化
nodejs 遵循 CommonJS 模块化规范使用 require () 导入模块,使用 module.exports 向外共享组件。
ES6 之前有 AMD、CMD、CommonJS 等模块化规范,但是 AMD、CMD 适合浏览器端的模块化,CommonJs 适合服务器端的模块化
# ES6 模块化规范
ES6 模块化规范是前后端通用的模块化开发规范。
在 ES6 中每个 js 文件都是一个独立的模块,
1. 导入模块成员需要用到 important 关键字
2. 使用 export 向外共享成员
nodejs 中默认支持 CommonJs 模块化规范,使用 ES6 模块化开发规范需要进行配置
1. 确保 nodejs 版本高于 14.15.1,低版本不支持
2. 在 package.json 中添加 'type'='module'
# ES6 模块化规范的基本语法
1. 默认导出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export default 默认导出的成员 let bk=1; let mk=3; function num (){ return mk+bk; } export default{ bk,mk,show }
|
# 1. 默认导入
TIP: export default 在一个模块中只允许使用一次
# 2. 按需导入与导出
导出
1 2 3 4 5 6 7 8
| 按需导出.js export let num1=12 export let num2=12 export let num3=12 export const show=()=>{ cl('这是方法') }
|
通过解构赋值的方式按需导入; 导入导出成员名必须一致才能提取到值
1
| import {show,num1} from '按需导出.js'
|
可以使用 as 重命名
1
| import {show as hs,num1} from '按需导出.js'
|
默认导出 export default 导出的多个成员也可以使用按需导入
3. 直接导入并执行模块中的代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| 当模块中只有写的函数时可以当作一个函数直接导出时: console.log('这是直接导入模块.js') ---------------------------------------------------------------------------- 导入 import '这是直接导入模块.js'
|
# Promise
# 1. 回调地狱
多层回调函数相互嵌套就形成了回调地狱;
代码耦合性太强,难以维护;可读性差
ES6 中新增 Promise 可以解决回调地狱问题
1. 构造函数 const pro =new Promise ()
pro 代表一个异步操作
2.Promise.prototype 上包含一个.then () 方法
pro.then()
3..then () 方法用来预先指定成功和失败的回调函数、
pro.then (result=>{成功},[error=>{失败}])
成功回调是必选,err 可选可不选
# then-fs 的基本使用
通过提供的 readFile () 方法可以异步的读取文件内容,返回值是 promise 的实例对象,可以调用。then () 方法处理回调函数
输出无法保证输出顺序,这是可以用到.then () 特性:当上一个.then () 中返回一个新的 promise 实例对象,则可以通过下一个.then () 处理,通过.then () 的链式调用可以解决回调地狱问题
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
| import thenFs from 'then-fs' thenFs .readFile('./111.txt', 'utf-8') .then((result) => { console.log(result) return thenFs.readFile('./222.txt', 'utf-8') }) .then((result) => { console.log(result) return thenFs.readFile('./333.txt', 'utf-8') }) .then((result) => { console.log(result) }) --------------------------------------------------------------------------------------- PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\test.js 1111111111111111111 222222222222222222 333333333333333333333333333 PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\test.js 1111111111111111111 222222222222222222 333333333333333333333333333 PS D:\down\桌面\Vue.js渐进式框架\ES6>
|
通过 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
| import thenFs from 'then-fs' thenFs .readFile('./1111.txt', 'utf-8') .then((result) => { console.log(result) return thenFs.readFile('./222.txt', 'utf-8') }) .then((result) => { console.log(result) return thenFs.readFile('./333.txt', 'utf-8') }) .then((result) => { console.log(result) }).catch((err)=>{ console.log(err); }) catch放最后可以捕获到所有fs中的错误,但是不会继续执行,需要继续执行需要放到每一个.then()的前面捕获错误 -------------------------------------------------------------------------------------- PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\test.js [Error: ENOENT: no such file or directory, open 'D:\down\桌面\Vue.js渐进式框架\ES6\1111.txt'] { errno: -4058, code: 'ENOENT', syscall: 'open', path: 'D:\\down\\桌面\\Vue.js渐进式框架\\ES6\\1111.txt' } undefined [Error: ENOENT: no such file or directory, open 'D:\down\桌面\Vue.js渐进式框架\ES6\2222.txt'] { errno: -4058, code: 'ENOENT', syscall: 'open', path: 'D:\\down\\桌面\\Vue.js渐进式框架\\ES6\\2222.txt' } undefined 333333333333333333333333333 PS D:\down\桌面\Vue.js渐进式框架\ES6> 即使前面两个都读取错误,最后一个仍然可以正常读取
|
# promise.all()
等待所有异步操作结束后调用。then 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import thenFs from 'then-fs' const promiseArr = [ thenFs.readFile('./111.txt', 'utf-8'), thenFs.readFile('./222.txt', 'utf-8'), thenFs.readFile('./333.txt', 'utf-8') ] Promise.all(promiseArr) .then(([r1, r2, r3]) => { console.log(r1, r2, r3) }) .catch((err) => { console.log(err) }) ------------------------------------------------------------------------------------------- PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\promise.all.js 1111111111111111111 222222222222222222 333333333333333333333333333 PS D:\down\桌面\Vue.js渐进式框架\ES6>
|
# Promise.race()
执行完一个异步操作,执行一次.then ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import thenFs from 'then-fs' const promiseArr = [ thenFs.readFile('./111.txt', 'utf-8'), thenFs.readFile('./222.txt', 'utf-8'), thenFs.readFile('./333.txt', 'utf-8') ] Promise.race(promiseArr) .then((result) => { console.log(result) }) .catch((err) => { console.log(err.message) }) ------------------------------------------------------------------------------------------ 33333333333333333333333333 PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\promise.race.js 222222222222222222 PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\promise.race.js 333333333333333333333333333 PS D:\down\桌面\Vue.js渐进式框架\ES6>
|
# 基于 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 34 35 36 37
| import fs from 'fs' 1.方法的名称要定义为getFile 2.接收一个形参fPath,表示读取文件的路径 function getFile(fPath) { 3.返回值是Promise的实例对象 return new Promise((res, rej) => { fs.readFile(fPath, 'utf8', (err, datar) => { if (err) return rej(err) return res(datar) }) }) }
getFile('./111.txt').then((result)=>{},(err)=>{}) -------------------------------------------------------------------------------------------S D:\down\桌面\Vue.js渐进式框架\ES6> node .\PromiseFS.js 1111111111111111111 [Error: ENOENT: no such file or directory, open 'D:\down\桌面\Vue.js渐进式框架\ES6\1111.txt'] { errno: -4058, code: 'ENOENT', syscall: 'open', path: 'D:\\down\\桌面\\Vue.js渐进式框架\\ES6\\1111.txt' } 失败 [Error: ENOENT: no such file or directory, open 'D:\down\桌面\Vue.js渐进式框架\ES6\1111.txt'] { errno: -4058, code: 'ENOENT', syscall: 'open', path: 'D:\\down\\桌面\\Vue.js渐进式框架\\ES6\\1111.txt' } PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\PromiseFS.js 失败 ENOENT: no such file or directory, open 'D:\down\桌面\Vue.js渐进式框架\ES6\1111.txt' PS D:\down\桌面\Vue.js渐进式框架\ES6>
|
# 什么是 async/await?(这里放五颗行星表示重点)
async/await 是 ES8 (ESMAScript2017) 引入的新语法,用来简化 promise 异步操作,ES8 之前只能通过链式.then () 来解决回调地狱,但是这种方法相对于 async/await 来说代码冗余,可读性方面都比较差,async 和 await 就完美的解决了该问题;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import thenFs from 'then-fs' async function getAbc() { const r1 = await thenFs.readFile('./111.txt', 'utf-8') console.log(r1) const r2 = await thenFs.readFile('./222.txt', 'utf-8') console.log(r2) const r3 = await thenFs.readFile('./333.txt', 'utf-8') console.log(r3) } getAbc() --------------------------------------------------------------------------------- PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\await.js 1111111111111111111 222222222222222222 333333333333333333333333333
|
await 会阻塞后续代码的执行直到当前成功或失败时才会继续执行
# EvevtLoop 同步和异步任务
1. 同步任务 TIP
非耗时任务,指的是在主线程上排队执行的任务
2. 异步任务
耗时任务,由 JavaScript 委托宿主环境执行,执行完毕后会通知主线程执行异步回调函数(定时器)
执行过程:按主线程次序执行当发现时一个异步任务的时候会放到宿主环境执行,先去执行同步任务,当宿主环境执行完后异步任务就会放到任务队列中等待执行,当调用栈被清空后会读取任务队列中的回调函数,按次序执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const a = '同步任务1' const b = '同步任务2' const c = '同步任务3' const d = '异步任务1' const e = '异步任务2' setTimeout(() => { console.log(d) }, 0.1) setTimeout(() => { console.log(e) }, 0.1) console.log(a) console.log(b) console.log(c)
|
先后多次实验从 500ms,200ms,100ms,1ms,0.1ms, 不管异步任务时间多短,总是先执行同步任务再执行异步任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\EvenLoop.js 同步任务1 同步任务2 同步任务3 异步任务1 PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\EvenLoop.js 同步任务1 同步任务2 同步任务3 异步任务2 PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\EvenLoop.js 同步任务1 同步任务2 同步任务3 异步任务1 异步任务2 PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\EvenLoop.js 同步任务1 同步任务2 同步任务3 异步任务1 异步任务2 PS D:\down\桌面\Vue.js渐进式框架\ES6>
|
看一下下面代码执行顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import thenFs from 'then-fs' console.log(1) async function getAbc() { console.log(2) const r1 = await thenFs.readFile('./111.txt', 'utf-8') console.log(r1) const r2 = await thenFs.readFile('./222.txt', 'utf-8') console.log(r2) const r3 = await thenFs.readFile('./333.txt', 'utf-8') console.log(r3) console.log(3) } getAbc() console.log(4)
|
我猜是 1243 执行:
1 2 3 4 5 6 7 8
| PS D:\down\桌面\Vue.js渐进式框架\ES6> node .\await.js 1 2 4 1111111111111111111 222222222222222222 333333333333333333333333333 3
|
# 宏任务和微任务
Javascript 把异步任务分为宏任务和微任务,
宏任务:异步 Ajax 请求,定时器,文件操作等
微任务:Promise.then (),.catch,.finally,process.nextTick 等
执行顺序
![]()
每一次宏任务执行完毕都会检查有没有微任务执行完所有微任务才会去执行下一个宏任务