ES6模块化与异步编程(Promise,async、await,EventLoop,宏任务和微任务)

ES6模块化与异步编程(Promise,async、await,EventLoop,宏任务和微任务),第1张

目录

ES6模块化

模块化介绍

1.在node.js中默认的模块化

2.ES6模块化规范

3.ES6模块的规范定义

4.在node.js中使用ES6模块化(在 package.json 的根节点中添加 "type": "module" 节点)

ES6模块化的使用

1.默认导出(export default 默认导出的成员)

2.默认导入(import 接收名称 from '模块标识符)

3.按需导出 (export 按需导出的成员)

4.按需导入(import { s1 } from '模块标识符')

5.直接导入并执行模块中的代码

Promise

Promise 解决的问题

1.回调地狱

2.Promise的基本概念

基于then-fs第三方包读取文件内容(支持Promise调用方法)

1.then-fs 的基本使用(readFile()方法异步读取文件,返回Promise实例对象)

2.then() 方法的特性(链式调用解决回调地狱问题)

3.通过 .catch 捕获错误

4.基于 Promise 按顺序读取文件的内容

5.Promise.all() 方法(等待机制:数组中 Promise 实例的顺序, 就是最终结果的顺序)

6.Promise.race() 方法(赛跑机制:谁执行快就拿谁作为结果)

 基于 Promise 封装读文件的方法

1 getFile 方法的基本定义和创建具体的异步 *** 作

2.获取 .then 的两个实参和调用 resolve 和 reject 回调函数

async/await(ES8)

1.async/await的基本使用

EventLoop(事件循环)

1. JavaScript 是单线程的语言

2. 同步任务和异步任务

3. 同步任务和异步任务的执行过程

4.JS执行机制

5.结合 EventLoop 分析输出的顺序

宏任务和微任务

1.宏任务和微任务的执行顺序

2.宏任务和微任务顺序案例

API接口案例(Promise应用)

实现步骤

1.搭建项目的基本结构

2.创建最基本的服务器(导入,创建实例,指定端口启动web服务器)

3.创建 db 数据库 *** 作模块

4.创建user_ctrl模块

5.创建API路由模块和在app.js进行导入注册


ES6模块化 模块化介绍 1.在node.js中默认的模块化 node.js 遵循了 CommonJS 的模块化规范。其中:
  •  导入其它模块使用 require() 方法
  • 模块对外共享成员使用 module.exports 对象
2.ES6模块化规范      在 ES6 模块化规范 诞生之前,JavaScript 社区已经尝试并提出了 AMD CMD CommonJS 等模块化规范。 但是,这些由社区提出的模块化标准,还是存在一定的 差异性 局限性 并不是 浏览器与服务器 通用的模块化 标准 ,例如:
  • AMD 和 CMD 适用于浏览器端的 Javascript 模块化
  • CommonJS 适用于服务器端的 Javascript 模块化
ES6 模块化规范诞生就是为了解决多种模块化不统一规范 ES6 模块化规范 浏览器端 服务器端 通用的模块化开发规范。它的出现极大的降低了前端开发者的模块化学习成本,开发者不需再额外学习 AMD、CMD 或 CommonJS 等模块化规范。 3.ES6模块的规范定义
  • 每个 js 文件都是一个独立的模块
  • 导入其它模块成员使用 import 关键字
  •  向外共享模块成员使用 export 关键字
4.在node.js中使用ES6模块化(在 package.json 的根节点中添加 "type": "module" 节点node.js 中 默认仅支持 CommonJS 模块化规范 ,若想基于 node.js 体验与学习 ES6 的模块化语法,可以按照 如下两个步骤进行配置:
  • ① 确保安装了 v14.15.1 或更高版本的 node.js
  • ② 在 package.json 的根节点中添加 "type": "module" 节点

ES6模块化的使用 ES6 的模块化主要包含如下 3 种用法: 默认导出 默认导入 按需导出 按需导入 直接导入 执行 模块中的代码 1.默认导出(export default 默认导出的成员 默认导出的语法: export default 默认导出的成员 每个模块中,只允许使用唯一的一次 export default,否则会报错!
let n1=10
let n2=20
function show(){}

/* 每个模块中,只允许使用唯一一次的默认导出export default */
export default{
  n1,
  show
}
2.默认导入(import 接收名称 from '模块标识符默认导入的语法: import 接收名称 from ' 模块标识符 默认导入时的接收名称可以任意名称,只要是合法的成员名称即可
import m1 from './01.默认导出.js'

console.log(m1);
3.按需导出 export 按需导出的成员按需导出的语法: export 按需导出的成员 每个模块中可以使用多次按需导出
//按需导出,可以导出多次
export let s1='dilireba'
export let s2='ccc'
export function say(){}

export default{
  a:20
}
4.按需导入(import { s1 } from '模块标识符'按需导入的语法: import { s1 } from '模块标识符'
  • 按需导入的成员名称必须和按需导出的名称保持一致
  • 按需导入时,可以使用 as 关键字进行重命名
  • 按需导入可以和默认导入一起使用
// 按需导入,名称必须跟按需导出的名称一样
//可以通过 as 进行重命名
//按需导入可以和默认导入一起用
import info,{ s1,s2 as str2,say } from './03.按需导出.js'
console.log(s1);
console.log(str2);
console.log(say);
console.log(info);
5.直接导入并执行模块中的代码 如果 只想单纯地执行某个模块中的代码 ,并不需要得到模块中向外共享的成员。此时,可以直接导入并执行模块代码

import  './05.直接运行模块中的代码.js'

05.文件中:

for(let i=0;i<3;i++){
  console.log(i);
}

Promise Promise 解决的问题 1.回调地狱 多层回调函数的相互嵌套 ,就形成了 回调地狱

回调地狱的缺点:
  • 代码耦合性太强,牵一发而动全身,难以维护
  • 大量冗余的代码相互嵌套,代码的可读性变差
为了解决回调地狱的问题,ES6(ECMAScript 2015)中新增了 Promise 的概念 2.Promise的基本概念 ① Promise 是一个 构造函数
  •  我们可以创建 Promise 的实例 const p = new Promise()
  • new 出来的 Promise 实例对象,代表一个异步 *** 作
② Promise.prototype 上包含一个 .then() 方法
  •  每一次 new Promise() 构造函数得到的实例对象,
  •  都可以通过原型链的方式访问到 .then() 方法,例如 p.then()
③ .then() 方法用来预先指定成功和失败的回调函数
  • p.then(成功的回调函数,失败的回调函数)
  •  p.then(result => { }, error => { })
  • 调用 .then() 方法时,成功的回调函数是必选的、失败的回调函数是可选的

 

基于then-fs第三方包读取文件内容(支持Promise调用方法) 由于 node.js 官方提供的 fs 模块 仅支持 回调函数的方式 读取文件, 不支持 Promise 的调用方式 。因此,需要先运行如下的命令,安装 then-fs 这个第三方包,从而支持我们基于 Promise 的方式读取文件的内容

装包:npm i then-fs

1.then-fs 的基本使用(readFile()方法异步读取文件,返回Promise实例对象) 调用 then-fs 提供的 readFile() 方法,可以异步地读取文件的内容, 它的返回值是 Promise 的实例对象 。因 此可以 调用 .then() 方法 为每个 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.then() 方法的特性(链式调用解决回调地狱问题) 如果上一个 .then() 方法中 返回了一个新的 Promise 实例对象 ,则可以通过下一个 .then() 继续进行处理。通过 .then() 方法的 链式调用 ,就解决了回调地狱的问题 3.通过 .catch 捕获错误 在 Promise 的链式 *** 作中如果发生了错误,可以使用 Promise.prototype. catch 方法进行捕获和处理 4.基于 Promise 按顺序读取文件的内容

Promise 支持链式调用,从而来解决回调地狱的问题

import thenFs from 'then-fs'

/* 调用 then-fs 提供的 readFile() 方法,可以异步地读取文件的内容
,它的返回值是 Promise 的实例对象。 */
// 1.在 Promise 的链式 *** 作中如果发生了错误,
//可以使用 Promise.prototype.catch 方法
// 2.如果不希望前面的错误导致后续的
// .then 无法正常执行,则可以将 .catch 的调用提前
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);
})
5.Promise.all() 方法(等待机制:数组中 Promise 实例的顺序, 就是最终结果的顺序 Promise.all() 方法会发起并行的 Promise 异步 *** 作,等 所有的异步 *** 作全部结束后 才会执行下一步的 .then *** 作(等待机制)。
import thenFs from 'then-fs'

/* Promise.all() 方法会发起并行的 Promise 异步 *** 作,
等所有的异步 *** 作全部结束后才会执行下一步的 .then 
 *** 作(等待机制)。 */
const promiseArr=[
  thenFs.readFile('./files/1.txt','utf8'),
  thenFs.readFile('./files/2.txt','utf8'),
  thenFs.readFile('./files/3.txt','utf8')
]

Promise.all(promiseArr).then(result=>{
  console.log(result);
})

 注意:数组中 Promise 实例的顺序, 就是最终结果的顺序

6.Promise.race() 方法(赛跑机制:谁执行快就拿谁作为结果 Promise.race() 方法会发起并行的 Promise 异步 *** 作, 只要任何一个异步 *** 作完成,就立即执行下一步的 .then *** 作 (赛跑机制)。
import thenFs from 'then-fs'

/* Promise.race() 方法会发起并行的 Promise 异步 *** 作,
只要任何一个异步 *** 作完成,就立即执行下一步的
.then  *** 作(赛跑机制)。谁执行快就拿谁作为结果 */
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);
})

 基于 Promise 封装读文件的方法 方法的封装要求:
  • 方法的名称要定义为 getFile
  • 方法接收一个形参 fpath,表示要读取的文件的路径
  • 方法的返回值为 Promise 实例对象

具体步骤:

1 getFile 方法的基本定义和创建具体的异步 *** 作
//方法接收一个形参fpath,表示要读取的文件路径
function getFile(fpath){
//返回的是一个Promise实例对象
  return new Promise()
}

这时new Promise()只是创建了一个形式上的异步 *** 作,如果想要创建具体的异步 *** 作,则需要在 new Promise() 构造函数期间,传递一个 function 函数,将具体的异步 *** 作定义到 function 函数内部。

function getFile(fpath){
  return new Promise(function(){
    //下面的代码表示这是一个读文件的异步 *** 作
    fs.readFile(fpath,'utf8',(err,dataStr)=>{ })
  })
}
2.获取 .then 的两个实参和调用 resolve 和 reject 回调函数 通过 .then() 指定的 成功 失败 的回调函数,可以在 function 的 形参中 进行接收, Promise 异步 *** 作的结果 ,可以调用 resolve reject 回调函数进行处理。
/* 基于Promise封装异步读文件的方法 */
import fs from 'fs'

/* resolve 是成功的回调函数,reject是失败的回调函数 */
/* err 是读取失败返回的结果,dataStr是读取成功返回的结果 */
function getFile(fpath){
  return new Promise(function(resolve,reject){
    fs.readFile(fpath,'utf8',(err,dataStr)=>{
      /* 如果读取失败。则调用失败回调函数 */
      if(err) return reject(err)
      /* 如果读取成功,则调用成功回调函数 */
      resolve(dataStr)
    })
  })
}

/* Promise 异步 *** 作的结果,可以调用 resolve 或 reject 回调函数进行处理 */


/* 方法测试 */
/* 通过 .then() 指定的成功和失败的回调函数,可以在 function 的形参中进行接收 */
getFile('./files/1.txt').then((r1)=>{
  console.log(r1);
},(err)=>{
  console.log(err.message);
})

//也可以通过catch捕获异常
getFile('./files/11.txt').then((r1)=>{
  console.log(r1);
}).catch((err)=>console.log(err.message))
async/await(ES8) async/await ES8 (ECMAScript 2017)引入的新语法,用来 简化 Promise 异步 *** 作 。在 async/await 出现之前,开发者只能通过 链式 .then() 的方式处理 Promise 异步 *** 作。
  • .then 链式调用的优点: 解决了回调地狱的问题
  • .then 链式调用的缺点: 代码冗余、阅读性差、不易理解
1.async/await的基本使用 使用 async/await 简化 Promise 异步 *** 作
import thenFs from 'then-fs'

/* async/await 是 ES8(ECMAScript 2017)引入的新语法,
用来简化 Promise 异步 *** 作。在 async/await 出现之前,
开发者只能通过链式 .then() 的方式处理 Promise 异步 *** 作 */

/* 如果方法的返回值是个Promise对象,可以用await修饰
这样就可以拿到读取到的值,r1不再是Promise对象,而是读取到的数据
注意:如果用了await(等待)进行修饰,则需要在方法前加async(异步)关键字进行修饰 */
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');
}

/* 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行 */

getAllFile()
console.log('C');

 如果方法的返回值是个Promise对象,可以用await修饰,这样就可以拿到读取到的值,r1不再是Promise对象,而是读取到的数据。
注意:

  • 如果用了await(等待)进行修饰,则需要在方法前加async(异步)关键字进行修饰
  • 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行
EventLoop(事件循环) 1. JavaScript 是单线程的语言 JavaScript 是一门 单线程执行 的编程语言。也就是说,同一时间只能做一件事情。 单线程执行任务队列的问题: 如果 前一个任务非常耗时 ,则后续的任务就不得不一直等待,从而导致 程序假死 的问题。 2. 同步任务和异步任务 为了防止某个 耗时任务 导致 程序假死 的问题,JavaScript 把待执行的任务分为了两类: 同步任务(synchronous)
  •  又叫做非耗时任务,指的是在主线程上排队执行的那些任务,同步任务都在主线程上执行,形成一个执行栈。
  • 只有前一个任务执行完毕,才能执行后一个任务
异步任务(asynchronous)
  •  又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行
  •  当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数
异步任务有以下三种类型:
  • 1、普通事件,如 click、resize 等
  • 2、资源加载,如 load、error 等
  • 3、定时器,包括 setInterval、setTimeout 等
  • 异步任务相关回调函数添加到任务队列中(任务队列也称为消息队列)。
3. 同步任务和异步任务的执行过程
  • ① 同步任务由 JavaScript 主线程次序执行
  • ② 异步任务委托给宿主环境执行
  • ③ 已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行
  • ④ JavaScript 主线程的执行栈被清空后,会读取任务队列中的回调函数,次序执行
  • ⑤ JavaScript 主线程不断重复上面的第 4 步

 由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环( event loop)。

4.JS执行机制

 1. 先执行执行栈中的同步任务。

2. 异步任务(回调函数)放入任务队列中。

3. 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。

5.结合 EventLoop 分析输出的顺序
import thenFs from 'then-fs'

console.log('A');
thenFs.readFile('./files/1.txt','utf8')
.then(dataStr=>{
  console.log('B');
})
setTimeout(()=>{
  console.log('C');
},0)
console.log('D');

//结果:ADCB
/* 
A和D属于同步任务
C 和 B 属于异步任务。
它们的回调函数会被加入到任务队列中,
等待主线程空闲时再执行,
C走个过程,因为设置了0毫秒
*/
宏任务和微任务 JavaScript 把异步任务又做了进一步的划分,异步任务又分为两类,分别是: ① 宏任务(macrotask)
  •  异步 Ajax 请求、
  •  setTimeout、setInterval、
  • 文件 *** 作
  • 其它宏任务
② 微任务(microtask)
  • Promise.then、.catch 和 .finally
  • process.nextTick
  • 其它微任务
1.宏任务和微任务的执行顺序

先执行同步任务,接着执行微任务,再执行第一个宏任务,

然后每一个宏任务执行完之后,都会检查是否存在待执行的微任务,

如果有,则执行完所有微任务之后,再继续执行下一个宏任务。

例如:两人去银行办业务的场景,只有一个柜员。 2.宏任务和微任务顺序案例

面试题1:

/* 先执行同步任务
再执行异步任务的微任务,
最终执行异步任务的宏任务 */

/* 4.延时器是异步任务中的宏任务 */
setTimeout(function(){
  console.log('1');
})

/* 1.new Promise里面的方法会立即执行,是一个同步任务 */
new Promise(function(resolve){
  console.log('2');
  resolve()
}).then(function(){
  console.log('3');
})
/* 3.Promise的.then方法是异步任务的微任务 */

/* 2.同步任务 */
console.log('4');

//2431
面试题2:
console.log('1');//同步任务
setTimeout(function(){//宏任务1
  console.log('2');//宏任务1中的第一个任务
  new Promise(function(resolve){
    console.log('3');
    resolve()//宏任务1中的第二个任务
  }).then(function(){
    console.log('4');//宏任务1中的微任务
  })
})

new Promise(function(resolve){
  console.log('5');
  resolve()//同步任务
}).then(function(){
  console.log('6');//微任务
})

setTimeout(function(){//宏任务2
  console.log('7');//宏任务2的第一个任务
  new Promise(function(resolve){
    console.log('8');//宏任务2的第二个任务
    resolve()
  }).then(function(){宏任务2的微任务
    console.log('9');
  })
})

//156234789

总结:先执行同步任务,

注意new Promise()function中立即调用属于同步任务,

然后检查在这个级别下有无微任务,如果有就执行微任务,

然后再执行第一个宏任务,第一个宏任务中的任务执行完,

检查该宏任务有无微任务,如果有就执行,再执行下一个宏任务,重复这样 *** 作。

API接口案例(Promise应用) 基于 MySQL 数据库 + Express 对外提供 用户列表 的 API 接口服务。用到的技术点如下:
  •  第三方包 express 和 mysql2
  • ES6 模块化
  • Promise
  • async/await
实现步骤
  • ① 搭建项目的基本结构
  • ② 创建基本的服务器
  • ③ 创建 db 数据库 *** 作模块
  • ④ 创建 user_ctrl 业务模块
  • ⑤ 创建 user_router 路由模块
1.搭建项目的基本结构 初始化项目:npm init -y 启用 ES6 模块化支持 :在 package.json 中声明 "type": "module" 安装第三方依赖包 :运行 npm install express@4.17.1 mysql2@2.2.5 2.创建最基本的服务器(导入,创建实例,指定端口启动web服务器)

在app.js入口文件创建基本的服务器

//导入express模块
import express from 'express'
//创建express的服务器实例
const app=express()

//调用app.listen方法,指定端口号并启动服务器
app.listen(80,()=>{
  console.log('server running at http://127.0.0.1');
})
3.创建 db 数据库 *** 作模块

创建db文件夹,在db文件夹下创建index.js

import mysql from 'mysql2'

const pool =mysql.createPool({
  host:'127.0.0.1',
  port:3306,
  database:'my_db_01',// *** 作的数据库名称
  user:'root',//登录数据库的用户名
  password:'admin123'//登录数据库的密码
})

//默认导出一个支持Promise API 的pool
export default pool.promise()
4.创建user_ctrl模块

新建一个文件夹controller进行封装获取用户列表的数据,在该文件夹下创建user_ctrl.js

模块,

其中用try.....catch进行捕获异常

import db from '../db/index.js'

/* 使用ES6的按需导出语法,将getAllUser方法导出出去 */
export async function getAllUser(req,res){
  try{
    const [rows]=await db.query('select id,username,nickname from ev_users')
  res.send({
    status:0,
    message:'获取用户列表数据成功!',
    data:rows
  })
  }catch(err){
    res.send({
      status:1,
      message:'获取用户列表数据失败!',
      desc:err.message
    })
  }
}

5.创建API路由模块和在app.js进行导入注册

创建文件夹router,该目录下创建user_router.js模块

import express from 'express'
import { getAllUser } from '../controller/user_ctrl.js'

const router=new express.Router()
router.get('/user',getAllUser)

export default router

在app.js进行导入注册

import express from 'express'
//导入路由模块
import userRouter from './router/user_router.js'

const app=express()

//路由模块进行注册使用
app.use('/api',userRouter)

app.listen(80,()=>{
  console.log('server running at http://127.0.0.1');
})

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/800319.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-06
下一篇 2022-05-06

发表评论

登录后才能评论

评论列表(0条)

保存