AJAX-3异步

AJAX-3异步

01.同步代码和异步代码

  1. 同步代码:逐行执行,需原地等待结果后,才继续向下执行

  2. 异步代码:调用后耗时,不阻塞代码继续执行(不必原地等待),在将来完成后触发回调函数传递结果

  3. 回答代码打印顺序:发现异步代码接收结果,使用的都是回调函数

    1
    2
    3
    4
    5
    6
    const result = 0 + 1
    console.log(result)
    setTimeout(() => {
    console.log(2)
    }, 2000)
    console.log(4)

    结果:1, 4, 2

02.回调函数地狱

  1. 需求:展示默认第一个省,第一个城市,第一个地区在下拉菜单中

    image-20230222173109762
  2. 概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱

  3. 缺点:可读性差,异常无法捕获,耦合性严重,牵一发动全身

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    axios({ url: 'http://hmajax.itheima.net/api/province' }).then(result => {
    const pname = result.data.list[0]
    document.querySelector('.province').innerHTML = pname
    // 获取第一个省份默认下属的第一个城市名字
    axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } }).then(result => {
    const cname = result.data.list[0]
    document.querySelector('.city').innerHTML = cname
    // 获取第一个城市默认下属第一个地区名字
    axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } }).then(result => {
    document.querySelector('.area').innerHTML = result.data.list[0]
    })
    })
    })

03.Promise-链式调用

  1. 概念:依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束

  2. 细节:then() 回调函数中的返回值,会影响新生成的 Promise 对象最终状态和结果

  3. 好处:通过链式调用,解决回调函数嵌套问题

    image-20230222173851738

  4. 按照图解,编写核心代码:

    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
    /**
    * 目标:掌握Promise的链式调用
    * 需求:把省市的嵌套结构,改成链式调用的线性结构
    */
    // 1. 创建Promise对象-模拟请求省份名字
    const p = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('北京市')
    }, 2000)
    })

    // 2. 获取省份名字
    const p2 = p.then(result => {
    console.log(result)
    // 3. 创建Promise对象-模拟请求城市名字
    // return Promise对象最终状态和结果,影响到新的Promise对象
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve(result + '--- 北京')
    }, 2000)
    })
    })

    // 4. 获取城市名字
    p2.then(result => {
    console.log(result)
    })

    // then()原地的结果是一个新的Promise对象
    console.log(p2 === p)

04.Promise-链式调用_解决回调地狱

  1. 目标:使用 Promise 链式调用,解决回调函数地狱问题

  2. 做法:每个 Promise 对象中管理一个异步任务,用 then 返回 Promise 对象,串联起来

    image-20230222174946534

  3. 按照图解思路,编写核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * 目标:把回调函数嵌套代码,改成Promise链式调用结构
    * 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
    */
    let pname = ''
    // 1. 得到-获取省份Promise对象
    axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {
    pname = result.data.list[0]
    document.querySelector('.province').innerHTML = pname
    // 2. 得到-获取城市Promise对象
    return axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
    }).then(result => {
    const cname = result.data.list[0]
    document.querySelector('.city').innerHTML = cname
    // 3. 得到-获取地区Promise对象
    return axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
    }).then(result => {
    console.log(result)
    const areaName = result.data.list[0]
    document.querySelector('.area').innerHTML = areaName
    })

05.async 函数和 await

  1. 概念:在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值

  2. 做法:使用 async 和 await 解决回调地狱问题

  3. 核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 目标:掌握async和await语法,解决回调函数地狱
    * 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
    * 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
    */
    // 1. 定义async修饰函数
    async function getData() {
    // 2. await等待Promise对象成功的结果
    const pObj = await axios({url: 'http://hmajax.itheima.net/api/province'})
    const pname = pObj.data.list[0]
    const cObj = await axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
    const cname = cObj.data.list[0]
    const aObj = await axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
    const areaName = aObj.data.list[0]


    document.querySelector('.province').innerHTML = pname
    document.querySelector('.city').innerHTML = cname
    document.querySelector('.area').innerHTML = areaName
    }

    getData()

    使用 await 替代 then 的方法

06.事件循环

  1. 事件循环(EventLoop):掌握后知道 JS 是如何安排和运行代码的

    请回答下面 2 段代码打印的结果,并说明原因

    1
    2
    3
    4
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 2000)
    1
    2
    3
    4
    5
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    console.log(3)
  2. 作用:事件循环负责执行代码,收集和处理事件以及执行队列中的子任务

  3. 原因:JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型

  4. 概念:执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 目标:阅读并回答执行的顺序结果
    */
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    console.log(3)
    setTimeout(() => {
    console.log(4)
    }, 2000)
    console.log(5)

    image-20230222182338992

07.事件循环-练习

  1. 需求:请根据掌握的事件循环的模型概念,分析代码执行过程

    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
    /**
    * 目标:阅读并回答执行的顺序结果
    */
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    function myFn() {
    console.log(3)
    }
    function ajaxFn() {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', 'http://hmajax.itheima.net/api/province')
    xhr.addEventListener('loadend', () => {
    console.log(4)
    })
    xhr.send()
    }
    for (let i = 0; i < 1; i++) {
    console.log(5)
    }
    ajaxFn()
    document.addEventListener('click', () => {
    console.log(6)
    })
    myFn()

结果:1 5 3 2 4 点击一次document就会执行一次打印6

08.宏任务与微任务

  1. ES6 之后引入了 Promise 对象, 让 JS 引擎也可以发起异步任务

  2. 异步任务划分为了

    • 宏任务:由浏览器环境执行的异步代码
    • 微任务:由 JS 引擎环境执行的异步代码
  3. 宏任务和微任务具体划分:

    image-20230222184920343

  4. 事件循环模型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 目标:阅读并回答打印的执行顺序
    */
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    const p = new Promise((resolve, reject) => {
    resolve(3)
    })
    p.then(res => {
    console.log(res)
    })
    console.log(4)
    // 1 4 3 2

注意:宏任务每次在执行同步代码时,产生微任务队列,清空微任务队列任务后,微任务队列空间释放!

下一次宏任务执行时,遇到微任务代码,才会再次申请微任务队列空间放入回调函数消息排队

总结:一个宏任务包含微任务队列,他们之间是包含关系,不是并列关系

执行第一个 script 脚本事件宏任务,里面同步代码
遇到 宏任务/微任务 交给宿主环境,有结果回调函数进入对应队列
当执行栈空闲时,清空微任务队列,再执行下一个宏任务,所以会先执行p.then再执行setTimeout

image-20240508203548061

9.事件循环 - 经典面试题

  1. 需求:请切换到对应配套代码,查看具体代码,并回答打印顺序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 目标:回答代码执行顺序
    console.log(1)
    setTimeout(() => {
    console.log(2)
    const p = new Promise(resolve => resolve(3))
    p.then(result => console.log(result))
    }, 0)
    const p = new Promise(resolve => {
    setTimeout(() => {
    console.log(4)
    }, 0)
    resolve(5)
    })
    p.then(result => console.log(result))
    const p2 = new Promise(resolve => resolve(6))
    p2.then(result => console.log(result))
    console.log(7)
    //1 7 5 6 2 3 4

10.Promise.all 静态方法

  1. 概念:合并多个 Promise 对象,等待所有同时成功完成(或某一个失败),做后续逻辑

    image-20230222190117045
  2. 语法:

    1
    2
    3
    4
    5
    6
    const p = Promise.all([Promise对象, Promise对象, ...])
    p.then(result => {
    // result 结果: [Promise对象成功结果, Promise对象成功结果, ...]
    }).catch(error => {
    // 第一个失败的 Promise 对象,抛出的异常对象
    })
  3. 需求:同时请求“北京”,“上海”,“广州”,“深圳”的天气并在网页尽可能同时显示

    image-20230222190230351
  4. 核心代码如下:

    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
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise的all方法</title>
    </head>

    <body>
    <ul class="my-ul"></ul>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
    /**
    * 目标:掌握Promise的all方法作用,和使用场景
    * 业务:当我需要同一时间显示多个请求的结果时,就要把多请求合并
    * 例如:默认显示"北京", "上海", "广州", "深圳"的天气在首页查看
    * code:
    * 北京-110100
    * 上海-310100
    * 广州-440100
    * 深圳-440300
    */
    // 1. 请求城市天气,得到Promise对象
    const bjPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '110100' } })
    const shPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '310100' } })
    const gzPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440100' } })
    const szPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440300' } })

    // 2. 使用Promise.all,合并多个Promise对象
    const p = Promise.all([bjPromise, shPromise, gzPromise, szPromise])
    p.then(result => {
    // 注意:结果数组顺序和合并时顺序是一致
    console.log(result)
    const htmlStr = result.map(item => {
    return `<li>${item.data.data.area} --- ${item.data.data.weather}</li>`
    }).join('')
    document.querySelector('.my-ul').innerHTML = htmlStr
    }).catch(error => {
    console.dir(error)
    })
    </script>
    </body>

    </html>
作者

步步为营

发布于

2024-05-08

更新于

2025-03-15

许可协议