一、回调函数(callback)
1、含义:是一种函数的调用方式
2、用于: 用于我们的代码的封装,一般多用于 异步代码的封装
- 异步代码: 定时器(一次性定时器 , 反复性定时器)
- 我们封装异步代码的时候 想要在结尾做一些事情的时候
- 这个时候我们一般使用的回调函数
- 运动函数: 结束的末尾做一些事情 , 使用的是函数
3、为什么要使用回调函数
- 因为我们的函数可以承载一段代码
- 就是我们的函数的即时调用性
4、什么是回调函数
- 就是把一个函数但是形参传递到另一个函数中
- 在另一个函数中以形参的方式调用
- 我们就把这个函数叫做另一个函数的回调函数
- 我们把一个函数A 当做形参传递到另一个函数B中
- 在函数B中以形参的方式调用这个函数
- 这个时候我们把 A函数称之为B函数的回调函数
二、回调地域
1、含义:回调函数的嵌套使用就会形成回调地狱
2、影响:代码的维护性不高以及代码的阅读性不高
3、解决办法:在ES6中出现一种新的封装异步代码的方式 ,叫做: Promise
- 是一种异步代码的封装方案
- 因为换了一种封装方案, 不需要按照回调函数的方式去调用
- 需要按照 Promise 的形式去调用
三、Promise
1、Promise的三个状态:
- 持续: pending 一种持续的状态
- 成功: fulfilled 从持续到成功
- 失败: rejected 从持续到失败
2、Promise的两种状态转换
- 从 持续状态 转化到 成功
- 从 持续状态 转化到 失败
3、Promise的基础语法:
- ES6 内的 内置构造函数
- 语法:const p = new Promise(function () {书写你异步的事情})
- p 就是一个 promise 实例对象
- promise 对象可以触发两个方法
=>p.then(函数) 成功的时候执行then()里面的函数
=>p.catch(函数) 失败的时候执行catch()里面的函数
=>这两个方法只是注册一个 成功 或者 失败 的时候会执行的函数
// Promise基础语法
const p = new Promise(function (resolve, reject) {
// 第一个形参: 是一个状态转换函数, 当你调用的时候, 会把当前 Promise 的状态转换为 成功
// 第二个形参: 是一个状态转换函数, 当你调用的时候, 会把当前 Promise 的状态转换为 失败
const time = Math.random() * 3000 + 2000
setTimeout(() => {
if (time < 3500) {
// console.log('失败')
// 表示本次 承若失败了, 我应该把本次 Promise 状态转换为失败
reject('打成猪头')
} else {
// console.log('成功')
// 表示本次 承若成功了, 我应该把本次 Promise 状态转换为成功
resolve('三珍海味')
}
}, time)
})
// 触发两个函数
p.then(function (address) {
// 当前函数会在本次 Promise 转换为成功的时候 触发
console.log('请你吃大餐', address)
})
p.catch(function (address) {
// 当前函数会在本次 Promise 转换为失败的时候 触发
console.log('我要揍你',address)
4、Promise的进阶语法
- 当你在第一个 then 里面返回(return) 一个新的 Promise 对象的时候
- 可以在第一个 then 后面继续第二个 then
jiehun()
.then(function (address) {
console.log('第一次 : ', '请你吃大餐', address)
return jiehun()
})
.then(function (address) {
console.log('第二次 : ', '请你吃大餐', address)
return jiehun()
})
.then(function (address) {
console.log('第三次 : ', '请你吃大餐', address)
})
.catch(function (address) {
// 当前函数会在本次 Promise 转换为失败的时候 触发
console.log('我要揍你',address)
})
四、async和await
1、是两个关键字
- 因为Promise解决回调地狱依旧不够友好
- 所以出现了async 和 await
- async: 异步的意思
- await: 等待的意思
注意: (1)需要配合的必须是 Promise 对象
·(2)Promise 语法的调用方案
意义: 把 异步代码 写的看起来 像 同步代码
2、async
- 直接书写在函数的前面, 表示该函数是一个 异步函数
- 只是把这个函数叫做异步函数 , 但是不改变这个函数本来的性质
- 也就是说之前是同步函数依旧是同步函数
- 之前是异步函数依旧是异步函数
- 意义: 表示在该函数内可以使用 await 关键字
3、await
- 必须书写在一个有 async 关键字的函数内
- await 后面等待的内容必须是一个 promise 对象
- 本该使用 then 接受的结果, 可以直接定义变量接受了
4、使用
// 按照 Promise 的形式封装代码
function jiehun() {
// Promise
const p = new Promise(function (resolve, reject) {
const time = Math.random() * 3000 + 2000
setTimeout(() => {
if (time < 3500) {
// console.log('失败')
// 表示本次 承若失败了, 我应该把本次 Promise 状态转换为失败
reject('打成猪头')
} else {
// console.log('成功')
// 表示本次 承若成功了, 我应该把本次 Promise 状态转换为成功
resolve('三珍海味')
}
}, time)
})
// 把本次 promise对象 返回出去
return p
}
// 通过async 和 await来使用
async function fn() {
// await 是等待的意思
// 在当前 fn 函数内, await 必须要等到后面的 Promise 结束以后, 才会继续执行后续代码
const r1 = await jiehun()
console.log('第一次 : ', r1)
const r2 = await jiehun()
console.log('第二次 : ', r2)
const r3 = await jiehun()
console.log('第三次 : ', r3)
}
fn()
五、Promise封装解决失败报错
因为我们上面的封装只能走成功,一旦报错就会阻止后续代码的执行,以下是解决办法
// 按照 Promise 的形式封装代码
function jiehun() {
// Promise
const p = new Promise(function (resolve, reject) {
const time = Math.random() * 3000 + 2000
setTimeout(() => {
if (time < 4500) {
// console.log('失败')
// 表示本次 承若失败了, 我应该把本次 Promise 状态转换为失败
reject('打成猪头')
} else {
// console.log('成功')
// 表示本次 承若成功了, 我应该把本次 Promise 状态转换为成功
resolve('三珍海味')
}
}, time)
})
// 把本次 promise对象 返回出去
return p
}
// 使用 async 和 await 的方式来调用
// async 和 await 语法的缺点
// await 只能捕获到 Promise 成功的状态
// 如果失败, 会报错, 终止程序继续执行
async function fn() {
const res = await jiehun()
console.log(res)
console.log('后续代码继续执行, 提示用户网络请求失败')
}
fn()
// 解决方案1: 使用 try catch 语法
// 语法: try { 执行代码 } catch(err) { 执行代码 }
// 首先执行 try 里面的代码, 如果不报错, catch 的代码不执行了
// 如果报错, 不会爆出错误, 不会终止程序执行, 而是执行 catch 的代码, 把错误信息给到 err 参数
try {
console.log(abc)
} catch (err) {
console.log('前面的代码失败了')
console.log(err)
}
async function fn() {
try {
const res = await jiehun()
console.log(res)
} catch (err) {
console.log('后续代码继续执行, 提示用户网络请求失败')
}
}
fn()
// 解决方案2: 改变封装的思路
// 原因: 因为 promise对象 有成功状态 和 失败状态
// 所以会在失败状态的时候, 报错
// 解决: 我就让当前的 Promise 对象百分百成功
// 让成功和失败都按照 resolve 的形式来执行
// 只不过传递出去的参数, 记录一个表示成功或者失败的信息
function jiehun() {
const p = new Promise(function (resolve, reject) {
const time = Math.random() * 3000 + 2000
setTimeout(() => {
if (time < 3500) {
resolve({ code: 0, message: '打成猪头' })
} else {
resolve({ code: 1, message: '三珍海味' })
}
}, time)
})
// 把本次 promise对象 返回出去
return p
}
// 使用的时候, 因为该 Promise 没有失败状态. 所以放心使用 awiat
async function fn() {
const r1 = await jiehun()
console.log(r1)
if (r1.code === 0) {
console.log('失败的逻辑, 提示用户错误')
}
const r2 = await jiehun()
console.log(r2)
if (r2.code === 0) {
console.log('失败的逻辑, 提示用户错误')
}
const r3 = await jiehun()
console.log(r3)
if (r3.code === 0) {
console.log('失败的逻辑, 提示用户错误')
}
}
fn()
六、Promise其他方法
1、then() 是成功的时候要执行的方法
2、catch() 是失败的时候要执行的方法
3、其它方法
=>finally() 完成的时候执行的方法
- 不管是成功还是失败都是成功
- 这个一般多使用在loading效果的时候
// 按照 Promise 的形式封装代码
function jiehun() {
// Promise
const p = new Promise(function (resolve, reject) {
const time = Math.random() * 3000 + 2000
setTimeout(() => {
if (time < 3500) {
// console.log('失败')
// 表示本次 承若失败了, 我应该把本次 Promise 状态转换为失败
reject('打成猪头')
} else {
// console.log('成功')
// 表示本次 承若成功了, 我应该把本次 Promise 状态转换为成功
resolve('三珍海味')
}
}, time)
})
// 把本次 promise对象 返回出去
return p
}
// finally()
jiehun()
.then(res => console.log(res))
.catch(err => console.log(err))
.finally(function () {
console.log('当前 Pomise 完成了')
})
七、Promise本身方法
1、all():
- 作用: 可以同时触发多个 Promise 行为
- 只有所有的 Promise 都成功的时候, all 才算成功
- 只要任何一个 Promise 失败的时候, all 就算失败了
- 语法: Promise.all([ 多个promise ])
2、race():
- 作用: 可以同时触发多个 Promise 行为
- 按照速度计算, 当第一个结束的时候就结束了
- 语法: Promise.race([ 多个promise ])
3、allSettled():(ES2020)
- 作用: 可以同时触发多个 Promise 行为
- 不管多个成功还是失败, 都会触发
- 会在结果内以数组的形式给你返回每一个 Promise 行为的成功还是失败
4、resolve():
- 强行返回一个成功状态的 promise对象
5、reject():
- 强行返回一个失败状态的 promise对象
// all()
Promise
.all([jiehun(),jiehun(),jiehun()])
.then(res => console.log(res))
.catch(err => console.log(err))
// race
Promise
.race([ jiehun(), jiehun(), jiehun() ])
.then(res => console.log(res))
.catch(err => console.log(err))
// allSettled
Promise
.allSettled([ jiehun(), jiehun(), jiehun(), jiehun(), jiehun(), jiehun() ])
.then(res => console.log(res))
// resolve()
Promise.resolve('成功').then(res => console.log(res))
// reject()
Promise.reject('失败').catch(err => console.log(err))
八、事件轮询
1、时间:从开始执行代码就开始执行轮询
2、规则
- 从 宏任务开始
- 每执行完毕 一个 空任务, 清空一次微任务队列(不管微任务队列内有多少任务, 都执行完毕)
- 再次执行一个 宏任务
- 循环往复, 直到所有任务队列清空结束
3、关键词
- 单线程: JS 是一个单线程的代码执行机制, 逐行依次执行代码, 会阻塞代码执行
- 调用栈: 用来执行代码的, 所有的代码进栈执行, 执行完毕出栈
- 队列: 用来存放异步任务的, 先进先出
- 宏任务队列: JS整体代码, setTimeout, setInterval, ...
- 微任务队列: Promise.then(), ...
- 轮询: 轮流询问 宏任务 和 微任务队列的任务来执行
注意: WEBApi, 用来负责提供异步机制, 计时, 分配任务去到指定队列
九、数组扁平化
1、就是把多维的数组转成一位的数组格式
const arr = [1, 2, [3, 4, [5, 6, [7, 8, [9,]]]]]
// res = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
// 方案1:
// 准备一个新数组
// 遍历原始数组, 依次插入到新数组内
// 因为不确定深度有多少层, 需要递归实现
function flat(origin) {
// 准备一个新数组
const ary = []
// 递归遍历原始数组
function fn(origin) {
origin.forEach(item => {
// 判断 item 如果是一个数组 - 递归
if (item.constructor === Array) {
// 把 item 内的所有内容拆开插入到 ary 内
fn(item)
// 判断 item 如果不是一个数组 - 插入 ary
} else {
ary.push(item)
}
})
}
// 把 origin 内的所有内容拆开插入到 ary 内
fn(origin)
// 返回 ary
return ary
}
// 使用的时候
// res 接受的就是一个扁平后的数组
const res = flat(arr)
console.log(res)
// 方案2: 利用 toString()
// 缺点: 尽量操作的全都是基本数据类型
// 数组.toString() 就是把数组的中括号全部去掉
// 注意: 这个方法的返回值是一个字符串, 需要再次使用 split() 拆开
const res1 = arr.toString().split(',')
console.log(res1)
// 方案3: 利用 flat 方法
// ES6 提供的数组常用方法
// 语法: 数组.flat(数字)
// 数字: 表示拆开多少层
const res2 = arr.flat(Infinity)
console.log(res2)