【AJAX】Promise详解

目录

[一、 同步和异步](#一、 同步和异步)

二、回调函数地狱

[三、 Promise链式调用](#三、 Promise链式调用)

[四、 async函数 和 await关键字](#四、 async函数 和 await关键字)

五、try......catch......用来捕获错误

[六、 事件循环------EventLoop](#六、 事件循环——EventLoop)

[七、 宏任务和微任务](#七、 宏任务和微任务)

[八、 Promise.all 静态方法](#八、 Promise.all 静态方法)

再详细解释一遍Promiss.all

[九、 案例------(二级数据请求)分类导航](#九、 案例——(二级数据请求)分类导航)

[总结不易~ 本章节对我有很大的收获,希望对你也是!!!](#总结不易~ 本章节对我有很大的收获,希望对你也是!!!)


本章节材料已经上传至Gitee:https://gitee.com/liu-yihao-hhh/ajax_studyhttps://gitee.com/liu-yihao-hhh/ajax_study

一、 同步和异步

  • 同步代码:逐行执行,需要原地等待结果后,才继续往下执行
  • 异步代码:调用后耗时,不阻塞代码继续执行(不必原地等待),在将来完成后触发一个回调函数

回答打印数字的顺序是什么?

javascript 复制代码
    const result = 0 + 1
    console.log(result)
    setTimeout(() => {
      console.log(2)
    }, 2000)
    document.querySelector('.btn').addEventListener('click', () => {
      console.log(3)
    })
    document.body.style.backgroundColor = 'pink'
    console.log(4)

答案:142 点一次打印一次3

说明这个定时器并不会阻塞代码执行,这个等倒计时结束后才会进行打印,倒计时的同时也在完成打印别的工作,说明这是一个异步代码。

异步代码接收结果:使用回调函数

二、回调函数地狱

  • 在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱
  • 缺点:可读性差,异常无法获取,耦合性严重,牵一发动全身
  • 回调函数地狱无法捕获里面的回调函数错误
javascript 复制代码
    /**
     * 目标:演示回调函数地狱
     * 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
     * 概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱
     * 缺点:可读性差,异常无法获取,耦合性严重,牵一发动全身
    */
    // 1. 获取默认第一个省份的名字
    axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {
      const pname = result.data.list[0]
      document.querySelector('.province').innerHTML = pname
      // 2. 获取默认第一个城市的名字
      axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }}).then(result => {
        const cname = result.data.list[0]
        document.querySelector('.city').innerHTML = cname
        // 3. 获取默认第一个地区的名字
        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
        })
      })
    }).catch(error => {
      console.dir(error)
    })

三、 Promise链式调用

将上面的回调地狱全部单独提炼出来,到.then中进行回调,可以解除回调地狱的问题

javascript 复制代码
    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)

四、 async函数 和 await关键字

async 和 await 关键字 让我们可以用一种更简洁的方式写出基于Promise的异步行为,无需刻意的链式调用Promise。

掌握async和await语法,解决回调函数地狱

  • 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
  • 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
javascript 复制代码
    // 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()

五、try......catch......用来捕获错误

  • try包裹可能产生错误的代码
  • 接着调用catch块,接收错误信息,如果try里某行代码报错后,try中剩余的代码不会执行了
javascript 复制代码
    async function getData() {
      // 1. try包裹可能产生错误的代码
      try {
        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
      } catch (error) {
        // 2. 接着调用catch块,接收错误信息
        // 如果try里某行代码报错后,try中剩余的代码不会执行了
        console.dir(error)
      }
    }

    getData()

故意传入错误服务器链接,打印错误!

六、 事件循环------EventLoop

  • JavaScript有一个基于事件循环的并发模型, 事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。
  • 原因:就是因为JavaScript单线程,为了让耗时代码不阻塞其他代码运行,设计了事件循环模型。

先来判断一下这两种情况打印的结果~

javascript 复制代码
    console.log(1)
    setTimeout(() => {
      console.log(2)
    }, 0)
    console.log(3)


    console.log(1)
    setTimeout(() => {
      console.log(2)
    }, 2000)
    console.log(3)

虽然第一个定时器是0秒,但是打印顺序都是132

来看这一个例子,在js内存在调用栈、宿主环境和任务队列

代码中存在异步操作的代码被放入宿主环境,宿主环境内异步时间结束,要开始被执行时,放入任务队列,等待被执行,这个时候,当调用栈内的同步代码被执行完毕后,任务队列的代码开始被调用栈调回开始执行!

七、 宏任务和微任务

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

异步任务分为:

  • 宏任务:由浏览器环境执行的异步代码
  • 微任务:由JS引擎环境执行的异步代码

调用栈会优先调用微任务队列,再调用宏任务队列

结果: 1 3 5 4 2

八、 Promise.all 静态方法

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

同时发送4个请求,同时展示4个数据,Promise.all合并多个Promise对象等待所有数据,要四个都成功后,才能到then中展示,只要有一个失败了,就要进入catch()

javascript 复制代码
    // 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)
    })

再详细解释一遍Promiss.all

Promise.all 的作用是: "等多个异步请求全部完成后,一起再处理它们的结果。"

你请求了一次一级分类,比如返回这样一组数据:

javascript 复制代码
[
  { id: 1, name: '居家' },
  { id: 2, name: '服饰' },
  { id: 3, name: '美食' }
]

然后你想:我拿到每个一级分类的 id,去请求它的二级分类,比如:

javascript 复制代码
axios({ url: '/api/category/sub', params: { id: 1 } }) // 居家的二级分类
axios({ url: '/api/category/sub', params: { id: 2 } }) // 服饰的二级分类
axios({ url: '/api/category/sub', params: { id: 3 } }) // 美食的二级分类

💥那问题来了:这些是异步请求,而且是多个!你怎么办?

你总不能这样写:这种写法会一个一个等 ,太慢了,不能并发

javascript 复制代码
const res1 = await axios(...) // id=1
const res2 = await axios(...) // id=2
const res3 = await axios(...) // id=3

这时候就轮到 Promise.all 上场了!

你可以一次性把这三个请求都发出去,然后写一句话:意思就是:等这些请求全都完成后,我再一起拿结果继续处理

javascript 复制代码
const allRes = await Promise.all([请求1, 请求2, 请求3])

你用 Promise.all([...]) 合并这些二级请求,是为了让它们并发执行,提高速度 ,然后等全部都完成后再统一渲染页面,这样用户体验更好,代码逻辑更清晰。

九、 案例------(二级数据请求)分类导航

二级数据请求就是用一级请求到的数据,以传送方发给服务器axios请求下一级的数据

为了不用每次请求到二级数据都要单独处理一次数据,我们就要用Promiss.all来合并所有的二级Promise对象

然后再单独采用map的形式生成一个专门存放html结构的数组.join('')后进行渲染

javascript 复制代码
    const demo = async () => {
      const response1 = await axios('http://hmajax.itheima.net/api/category/top')
      const data1 = response1.data.data
      console.log(data1)

      const data2 = data1.map(item => {
        return axios('http://hmajax.itheima.net/api/category/sub', { params: { id: item.id } })
      })

      console.log(data2)

      // 合并所有的二级分类Promise对象
      const p = Promise.all(data2)
      console.log(p)
      p.then(result => {
        console.log(result)
        // 等待同时成功后, 渲染页面
        const htmlStr = result.map((item, index) => {
          const dataObj = item.data.data // 取出关键数据对象
          console.log(dataObj)
          return `
            <div class="item">
              <h3>${dataObj.name}</h3>
              <ul>
                ${dataObj.children.map(item => {
            return `
                    <li>
                      <a href="javascript:;">
                        <img src="${item.picture}">
                        <p>${item.name}</p>
                      </a>
                    </li>
                  `
          }).join('')}
              </ul>
            </div>
          `
        }).join('')
        console.log(htmlStr)
        document.querySelector('.sub-list').innerHTML = htmlStr
      })



    }
    demo()

总结不易~ 本章节对我有很大的收获,希望对你也是!!!

相关推荐
五点六六六8 分钟前
前端常见的性能指标采集
前端·性能优化·架构
吳所畏惧10 分钟前
NVM踩坑实录:配置了npm的阿里云cdn之后,下载nodejs老版本(如:12.18.4)时,报404异常,下载失败的问题解决
前端·windows·阿里云·npm·node.js·batch命令
陈随易25 分钟前
AI新技术VideoTutor,幼儿园操作难度,一句话生成讲解视频
前端·后端·程序员
Pedantic28 分钟前
SwiftUI 按钮Button:完整教程
前端
前端拿破轮30 分钟前
2025年了,你还不知道怎么在vscode中直接调试TypeScript文件?
前端·typescript·visual studio code
代码的余温33 分钟前
DOM元素添加技巧全解析
前端
JSON_L36 分钟前
Vue 电影导航组件
前端·javascript·vue.js
用户214118326360243 分钟前
01-开源版COZE-字节 Coze Studio 重磅开源!保姆级本地安装教程,手把手带你体验
前端
大模型真好玩1 小时前
深入浅出LangChain AI Agent智能体开发教程(四)—LangChain记忆存储与多轮对话机器人搭建
前端·人工智能·python
帅夫帅夫1 小时前
深入理解 JWT:结构、原理与安全隐患全解析
前端