目录
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
=> 失败: rejectedconst 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