目录
1.?回调函数?callback
2.?回调地狱
3.?认识?Promise
4.?Promise 的进阶语法
5.?async?函数?和?await?关键字
Promise 简图 :
1.?回调函数?callback
??+?一种封装代码的手段
??+?什么是?callback?,?概念
????=>?把?函数A?当做?实参?传递到?函数B?内部
????=>?在?函数B?内部以?形参?的方式?调用?函数A
????=>?我们管这个行为叫做?回调函数
????=>?我们说?函数A?是?函数B?的 回调函数
function A() {
console.log('我是 A 函数')
}
function B(fn) {
// 此时 fn 形参接受的是书写在 B() 的时候, () 内部的内容 : A
// 此时 fn 形参接受的就是全局 函数 A 的地址
// 此时 fn 形参和全局变量 A 操作一个函数空间
console.log('我是 B 函数')
// 调用 fn 其实就是在调用执行全局的 A 函数
fn()
}
// 调用 B 函数
// A 是一个保存 函数的地址
// 把 函数 A 这个地址当做实参传递给了 B 函数内部的 fn 形参
B(A)
// 函数A?是?函数B?的回调函数
??+?为什么需要?callback?回调函数
??+?如果从头到尾都是 同步代码,?不需要回调函数
????=>?当你在 封装代码 的时候
????=>?并且代码内有 异步 的时候
????=>?并且需要在?异步的?末尾?做一些事情的时候
????=>?使用?callback
解释:?为什么异步的末尾封装要使用?callback
??+?因为?JS?的单线程
??+?同一个时间点只能做一个事情
??+?主要:?异步的结束时间不确定
??+?例子:?外卖
????=>?一个外卖员同一个时间点只能做一件事情
????=>?如果你希望多带一双筷子
????=>?方案1:?等到外卖员刚好到达店里的时候,?给他打电话
????=>?方案2:?在点餐的时候给一个备注
????回调函数的缺点:
??+?回调地狱
??+?当回调 嵌套 回调的时候,?代码的阅读和可维护性不高
??解决回调地狱的问题:
??+?Promise?来解决回调地狱
??+?分析:
????=>?Promise?是来解决回调地狱
????=>?回调地狱,?是因为回调函数嵌套过多
????=>?回调函数,?为了解决在异步末尾做一些事情的封装
????=>?Promise?就是一种优雅的对于异步代码封装的方案
// 为什么需要回调函数
// 封装一段代码
// 例子 : 外卖公司做好的事情
function waimai(beizhu) {
// 获取一个 1000 ~ 6000 的随机整数
const time = 1000 * Math.round(Math.random() * 5 + 1)
console.log(' 在路上 ' + time)
// 我们使用 setTimeout 模拟一个网络环境请求
setTimeout(() => {
console.log('到达店里了, 拿到外卖')
// 直接把我需要执行的代码放在这个位置
// 那么这个封装就没有意义了
// 就需要用到回调函数了
// 因为这个位置是异步的末尾了
// 这个位置调用 beizhu 就是在异步的末尾调用
// 例 : 不管什么时候到了店里
// 拿到外卖以后, 把 备注 的内容执行一下
beizhu()
}, time)
}
// 用户的需求: 想多拿一双筷子
waimai(function () { console.log('多拿一双筷子') })
// 用户的需求: 想多拿点辣椒
waimai(function () { console.log('多拿点辣椒') })
?
2.?回调地狱
??+?一种使用回调函数封装的代码时候的情况
??+?回调函数的使用是有 函数嵌套 在里面的
??+?当你大量使用回调函数封装的代码的时候,?会出现 结构紊乱
????=>?不利于代码的阅读和维护
??+?为了解决回调地狱
????=>?ES6?的语法内出现了一个新的语法,?叫做?Promise
????=>?为了把?异步代码?封装变成?Promise?语法的封装
????=>?不在使用?回调函数?来封装?异步代码了
????=>?本质: 用来?封装异步代码?的
?实现需求?:
1.?发送一个请求,?请求一个接口
??=>?等到响应回来以后
??=>?把内容打印在控制台
2.?发送第二个请求,?请求第二个接口
??=>?要求必须要在第一个请求结束以后,?打印完毕以后再次发送请求
??=>?把响应内容打印在控制台
3.?发送第三个请求,?请求第三个接口
??=>?要求,?必须要在第二个请求结束以后,?打印完毕以后再次发送请求
??=>?把响应内容打印在控制台
+?问题1:?在什么位置发送第二个请求??
=>?ajax?是同步还是异步,?异步的
=>?打开页面,?会马上把第一个请求发送出去
=>?第一个请求还没有回来的时候,?继续发送了第二个请求
=>?因为第一个请求的?success?一定会在请求结束后才执行
=>?我需要把第二个请求的代码放在第一个请求的?success?内部
问题:?代码的阅读和可维护性不高
+?代码嵌套过多
// 实现需求 1 :
ajax({
url: 'http://localhost:8888/test/first',
success: function (res) {
console.log('第一次请求的结果')
console.log(res)
// 实现需求 2 :
// 第一个请求结束以后, 才会执行这个位置的代码
// 这个时候才会把第二个请求发送出去
ajax({
url: 'http://localhost:8888/test/second',
dataType: 'json',
success: function (res) {
console.log('第二次请求的结果')
console.log(res)
// 实现需求 3 :
// 因为第二个请求结束以后, 才会执行这个位置的代码
// 这个时候才会把第三个请求发送出去
ajax({
url: 'http://localhost:8888/test/third',
data: 'name=Jack&age=18',
dataType: 'json',
success: function (res) {
console.log('第三次请求的结果')
console.log(res)
}
})
}
})
}
})
3.?认识?Promise
+?是一个?ES6?出现的语法
+?Promise?也是一个?JS?内置的 构造函数
promise - 承诺 : + 承诺的状态有多少个 ? ? => 继续(持续执行过程中) ? => 成功 ? => 失败 + 承诺状态之间的转换 : 只能转换一次 ? => 要么是 继续 转换成 成功 ? => 要么是 继续 转换成 失败 + Promise 也有三个状态 ? => 继续: pending ? => 成功: fulfilled ? => 失败: rejected?
const p = new Promise(function (resolve, reject) {
// ...
// ...
})
p.then(function () {
// ...
})
p.catch(function () {
// ...
})
Promise?的基础语法 :
??=>?const?p?=?new?Promise(function?a( )?{
????//?你要封装的异步代码
??})
??=>?promise?对象可以调用两个方法
????1.?p.then(function?( )?{ })
????2.?p.catch(function?( )?{ })
??+?promise对象.then(function?( )?{?...?})
????=>?给当前这个承诺注册一个?成功以后的函数
??+?promise对象.catch(function?( )?{?...?})
????=>?给当前这个承诺注册一个?失败以后的函数
??如何改变?promise?的状态
??+?在?new?Promise?的?a?函数内
??+?可以接受两个参数
????1.?第一个参数:?可以将该?Promise?的状态由继续转换为?成功
????2.?第二个参数:?可以将该?Promise?的状态由继续转换为?失败
// 1. 异步代码
const p = new Promise(function (resolve, reject) {
// resolve 就是一个转换成功的方法
// 当你书写 resolve() 的时候, 就是在把 该 promise 的状态转换为成功
// 就会执行 .then 时候里面书写的 b 函数
// reject 就是一个转换成失败的方法
// 当你书写 reject() 的时候, 就是在把 该 promise 的状态转换为失败
// 就会执行 .catch 时候里面书写的 c 函数
// 这两个只能书写一个
// 书写你需要封装的异步代码
const time = 1000 * Math.round(Math.random() * 5 + 1)
console.log('承诺一辈子在一起')
setTimeout(() => {
// 下面代码一旦执行, 就会把 promise 的状态改成成功
// resolve()
// 下面代码一旦执行, 就会把 promise 的状态改成失败
// reject()
if (time >= 3000) {
// 因为会转换为成功 => 两人去世, 埋一个坟
// 通知一下, 你该烧纸了
// resolve() 调用的是 then 内部的函数 b
// 所以这里书写在 () 内部的 time 内容就是给到 then 内 b 的实参
resolve(time)
} else {
// 当做失败, 表示离婚
// 通知一下, 该去炸坟了
// reject() 调用的是 catch 内部的函数 c
// 所以这里书写在 () 内部的 time 内容就是给到 catch 内 c 的实参, 也是报错信息
reject(time)
}
}, time)
})
// promise 对象调用的两个方法
// 注册 成功
p.then(function b(t) {
// 函数 b 不会被直接调用的
// 这个位置的代码会在 p 这个 promise 的状态由 继续 转换为 成功 的时候调用执行
console.log(t, '成功的函数 b')
// t 就是你在 promise 内部书写的 resolve 的小括号里面 time 的内容
})
// 注册 失败
p.catch(function c(err) {
// 函数 c 不会被直接调用
// 这个位置的代码会在 p 这个 promise 的状态由 继续 转换为 失败 的时候调用执行
console.log(err, '失败的函数 c')
})
?
4.?Promise 的进阶语法
+?当一个?Promise?的?then?内的代码
+?只要你在前一个?then?内部以?return?返回一个新的?promise?对象?的时候
+?新?promise?对象的?then?可以直接在前一个?then?的后面继续书写?then
?需求:
??1.?发送一个请求, 请求第一个接口
??2.?发送第二个请求, 请求第二个接口
????=>?前提:?必须要等到第一个请求结束以后再次发送
// 初始版 :
// 需求 1 :
const p = new Promise(function (resolve, reject) {
// 做异步的事情
ajax({
url: 'http://localhost:8888/test/first',
success: function (res) {
// 第一个请求成功了
// res 就是后端给出的结果
resolve(res)
}
})
})
p.then(function (res) {
console.log('第一次请求结束了')
console.log(res)
// 需求2:
const p2 = new Promise((resolve, reject) => {
// 做异步的事情
ajax({
url: 'http://localhost:8888/test/second',
dataType: 'json',
success: function (res) {
// 第一个请求成功了
// res 就是后端给出的结果
resolve(res)
}
})
})
p2.then(res => {
console.log('第二个请求结束了')
console.log(res)
})
})
// 进阶版 :
const p = new Promise((resolve, reject) => {
// 做异步的事情
ajax({
url: 'http://localhost:8888/test/first',
success: function (res) {
resolve(res)
}
})
})
// 向拿到 p 的 resolve 的结果
// 就得写 p.then()
p
.then(function (res) {
console.log('第一次请求的结果')
console.log(res)
// 做第二个事情
const p2 = new Promise((resolve, reject) => {
ajax({
url: 'http://localhost:8888/test/second',
dataType: 'json',
success: function (res) {
resolve(res)
}
})
})
// 在第一个 then 内部 return 一个 新的 promise 对象 p2
return p2
})
.then(res => {
console.log('第二次的请求结果')
console.log(res)
})
// 使用我按照 promise 形式封装的 pAjax 函数来完成
pAjax({ url: 'http://localhost:8888/test/first' })
.then(res => {
console.log('第一个请求结束了')
console.log(res)
// return 一个新的 promise 对象
return pAjax({
url: 'http://localhost:8888/test/second',
dataType: 'json'
})
})
.then(res => {
console.log('第二个请求结果')
console.log(res)
// return 一个新的 promise 对象
return pAjax({
url: 'http://localhost:8888/test/third',
data: 'name=Jack&age=20',
dataType: 'json'
})
})
.then(res => {
console.log('第三次请求的结果')
console.log(res)
})
5.?async?函数?和?await?关键字
?+?ES7?~?ES8?之间出现的语法
+?作用?:
+?为了解决?Promise?的问题?,?把?Promise?的代码书写的更优雅
+?核心作用:?把?异步代码?写的?看起来像?同步代码,?本质还是异步
语法:
??=>?async?关键字?(异步)
??+?使用:?书写在函数的前面
??(可以是声明式函数,?可以是函数表达式,?可以是箭头函数)
????=>?async?function?( )?{ }
????=>?async?( )?=>?{ }
??+?作用:
????1.?该函数内可以使用?await?关键字了
????2.?会把该函数变成一个?异步函数,?只是叫做?异步函数
????(这个异步函数并不是我们真实的异步代码,只是给这个函数起了个名字)
??????=>?影响的是函数内部的代码?,?不影响函数外面的代码
// async 的语法
async function fn() {}
const fn = async function () {}
const fn = async a => {}
=>?await?关键字?(等待)
??+?要求:
????1.?await?必须写在一个有?async?关键字的异步函数内部
????2.?await?后面等待的内容必须是一个?promise?对象?,?否则等不了
??+?作用:
????=>?把?promise?中本该在?then?内代码接受的结果?,?
????可以直接在?await?前面定义变量接受
????=>?后续的代码需要等到?promise?执行完毕才会执行
console.log('start') // ① start
async function fn() {
console.log('我是 fn 函数内部的代码') // ②
// 因为 pAjax 是按照 promise 的语法形式进行封装的代码
// pAjax 会返回一个 promise 对象
// fn 函数内, 执行到 pAjax 这个代码的时候
// 会等待, 等到这个异步的代码完全执行完毕, 把结果赋值给 r1 以后
// 在继续执行后面的代码
const r1 = await pAjax({ url: 'http://localhost:8888/test/first' })
console.log(r1) // ④
}
fn()
console.log('end') // ③ end
console.log('start') // 一 : start
async function fn() {
// 此时 fn 函数内可以使用 await 关键字了
// pAjax 返回出来的 promise 对象会执行
// 把 resolve() 的时候 括号里面的内容 赋值给 r1. 在继续向后执行代码
const r1 = await pAjax({ url: 'http://localhost:8888/test/first' })
console.log(r1) // 三 :
// 实现需求2:
const r2 = await pAjax({
url: 'http://localhost:8888/test/second',
dataType: 'json'
})
console.log(r2) // 四 :
// 实现需求3:
const r3 = await pAjax({
url: 'http://localhost:8888/test/third',
data: 'name=Jack&age=20',
dataType: 'json'
})
console.log(r3) // 五 :
}
fn()
console.log('end') // 二 : end
Promise 简图 :
|