Ajax(二):Bootstrap、XHR、Promise、同步、异步代码、回调函数地狱、事件循环、宏任务微任务、Promise.all方法、token、axios请求拦截器和响应拦截器

1、Bootstrap弹框

Modal文档

功能:不离开当前页面,显示单独内容,供用户操作。

步骤:

        1. 引入bootstrap.css和bootstrap.js

        2. 准备弹框标签,确认结构

        3. 通过自定义属性,控制弹框的显示和隐藏

通过属性控制,弹框显示或隐藏

  <!-- 
    目标:使用Bootstrap弹框
    1. 引入bootstrap.css 和 bootstrap.js
    2. 准备弹框标签,确认结构
    3. 通过自定义属性,控制弹框的显示和隐藏
   -->
    <!-- data-bs-toggle="modal" 点击弹框后出现modal的弹框 -->
    <!-- data-bs-target="css选择器" 点击对应控制哪个弹框-->
  <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target=".my-box">
    显示弹框
  </button>

  <!-- 
    弹框标签
    bootstrap的modal弹框,添加modal类名(默认隐藏)
   -->
  <div class="modal my-box" tabindex="-1">
    <div class="modal-dialog">
      <!-- 弹框-内容 -->
      <div class="modal-content">
        <!-- 弹框-头部 -->
        <div class="modal-header">
          <h5 class="modal-title">Modal title</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <!-- 弹框-身体 -->
        <div class="modal-body">
          <p>Modal body text goes here.</p>
        </div>
        <!-- 弹框-底部 -->
        <div class="modal-footer">
          <!-- data-bs-dismiss="modal" 隐藏modal提示框 -->
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
          <button type="button" class="btn btn-primary">Save changes</button>
        </div>
      </div>
    </div>
  </div>

  <!-- 引入bootstrap.js -->
  <script src="https://cdn.jsdelivr.***/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js"></script>

Modal文档通过JS控制,弹框显示或隐藏

  <!-- 
    目标:使用JS控制弹框,显示和隐藏
    1. 创建弹框对象
    2. 调用弹框对象内置方法
      .show() 显示
      .hide() 隐藏
   -->
  <button type="button" class="btn btn-primary edit-btn">
    编辑姓名
  </button>

  <div class="modal name-box" tabindex="-1">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">请输入姓名</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <form action="">
            <span>姓名:</span>
            <input type="text" class="username">
          </form>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
          <button type="button" class="btn btn-primary save-btn">保存</button>
        </div>
      </div>
    </div>
  </div>

  <!-- 引入bootstrap.js -->
  <script src="https://cdn.jsdelivr.***/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js"></script>
  <script>
    // 1. 创建弹框对象
    const modalDom = document.querySelector('.name-box')
    const modal = new bootstrap.Modal(modalDom)

    // 编辑姓名->点击->赋予默认姓名->弹框显示
    document.querySelector('.edit-btn').addEventListener('click', () => {
      document.querySelector('.username').value = '默认姓名'

      // 2. 显示弹框
      modal.show()
    })

    // 保存->点击->->获取姓名打印->弹框隐藏
    document.querySelector('.save-btn').addEventListener('click', () => {
      const username = document.querySelector('.username').value
      console.log('模拟把姓名保存到服务器上', username)

      // 2. 隐藏弹框
      modal.hide()
    })
  </script>

2、XMLHttpRequest - Ajax原理

为什么学XMLHttpRequest ?

1. 在静态网站中,与服务器交互的地方可能只有1~2处,而且我想让静态网站缩小一下它的体积,因此没有必要引入axios。

2. 可以了解axios内部怎样与服务器进行数据交互,了解axios内部原理。

使用XHR
  <p class="my-p"></p>
  <script>
    /**
     * 目标:使用XMLHttpRequest对象与服务器通信
     *  1. 创建 XMLHttpRequest 对象
     *  2. 配置请求方法和请求 url 地址
     *  3. 监听 loadend 事件,接收响应结果
     *  4. 发起请求
    */
    // 1. 创建 XMLHttpRequest 对象
    const xhr = new XMLHttpRequest()

    // 2. 配置请求方法和请求 url 地址
    xhr.open('GET', 'http://hmajax.itheima.***/api/province')

    // 3. 监听 loadend 事件,接收响应结果
    xhr.addEventListener('loadend', () => {
      // 返回的是 JSON 字符串
      console.log(xhr.response)
      // 将 JSON 字符串转换为对象
      const data = JSON.parse(xhr.response)
      console.log(data.list.join('<br>'))
      document.querySelector('.my-p').innerHTML = data.list.join('<br>')
    })

    // 4. 发起请求
    xhr.send()
  </script>
查询参数

定义:浏览器提供给服务器的额外信息,让服务器返回浏览器想要的数据

  <p class="city-p"></p>
  <script>
    /**
     * 目标:使用XHR携带查询参数,展示某个省下属的城市列表
    */
    const xhr = new XMLHttpRequest()
    xhr.open('GET', 'http://hmajax.itheima.***/api/city?pname=辽宁省')
    xhr.addEventListener('loadend', () => {
      console.log(xhr.response)
      const data = JSON.parse(xhr.response)
      console.log(data)
      document.querySelector('.city-p').innerHTML = data.list.join('<br>')
    })
    xhr.send()
  </script>
案例:查询地区列表
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-***patible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>案例_地区查询</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.***/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
  <style>
    :root {
      font-size: 15px;
    }

    body {
      padding-top: 15px;
    }
  </style>
</head>

<body>
  <div class="container">
    <form id="editForm" class="row">
      <!-- 输入省份名字 -->
      <div class="mb-3 col">
        <label class="form-label">省份名字</label>
        <input type="text" value="北京" name="province" class="form-control province" placeholder="请输入省份名称" />
      </div>
      <!-- 输入城市名字 -->
      <div class="mb-3 col">
        <label class="form-label">城市名字</label>
        <input type="text" value="北京市" name="city" class="form-control city" placeholder="请输入城市名称" />
      </div>
    </form>
    <button type="button" class="btn btn-primary sel-btn">查询</button>
    <br><br>
    <p>地区列表: </p>
    <ul class="list-group">
      <!-- 示例地区 -->
      <li class="list-group-item">东城区</li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.***/npm/axios/dist/axios.min.js"></script>
  <script>
    /**
     * 目标: 根据省份和城市名字, 查询对应的地区列表
    */
    // 1. 查询按钮-点击事件
    document.querySelector('.sel-btn').addEventListener('click', () => {
      // 2. 收集省份和城市名字
      const pname = document.querySelector('.province').value
      const ***ame = document.querySelector('.city').value

      // 3. 组织查询参数字符串
      const qObj = {
        pname,
        ***ame
      }
      // 查询参数对象 -> 查询参数字符串
      const paramsObj = new URLSearchParams(qObj)
      const queryString = paramsObj.toString()
      console.log(queryString)

      // 4. 使用XHR对象,查询地区列表
      const xhr = new XMLHttpRequest()
      xhr.open('GET', `http://hmajax.itheima.***/api/area?${queryString}`)
      xhr.addEventListener('loadend', () => {
        console.log(xhr.response)
        const data = JSON.parse(xhr.response)
        console.log(data)
        const htmlStr = data.list.map(areaName => {
          return `<li class="list-group-item">${areaName}</li>`
        }).join('')
        console.log(htmlStr)
        document.querySelector('.list-group').innerHTML = htmlStr
      })
      xhr.send()
    })
  </script>
</body>

</html>
数据提交

        请求头:可以设置一些额外信息给服务器。下面设置后相当于告诉服务器,这次传递的请求体的内容类型是JSON字符串,服务器那边就可以正确的把传过去的JSON字符串再转回去用。

        请求体:携带JSON字符串

  <button class="reg-btn">注册用户</button>
  <script>
    /**
     * 目标:使用xhr进行数据提交-完成注册功能
    */
    document.querySelector('.reg-btn').addEventListener('click', () => {
      const xhr = new XMLHttpRequest()
      xhr.open('POST', 'http://hmajax.itheima.***/api/register')
      xhr.addEventListener('loadend', () => {
        console.log(xhr.response)
      })

      // 设置请求头-告诉服务器内容类型(JSON字符串)
      xhr.setRequestHeader('Content-Type', 'application/json')
      // 准备提交的数据
      const userObj = {
        username: 'itheima007',
        password: '7654321'
      }
      const userStr = JSON.stringify(userObj)
      // 设置请求体,发起请求
      xhr.send(userStr)
    })
  </script>

3、Promise

定义:Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值。

好处:逻辑更清晰(成功和失败状态,可以关联对应的处理程序)、了解axios函数内部运作机制、Promise链式调用方式能解决回调函数地狱问题

  <script>
    /**
     * 目标:使用Promise管理异步任务
    */
    // 1. 创建Promise对象
    const p = new Promise((resolve, reject) => {
      // 2. 执行异步代码
      setTimeout(() => {
        // resolve('模拟AJAX请求-成功结果')
        reject(new Error('模拟AJAX请求-失败结果'))
      }, 2000)
    })

    // 3. 获取结果
    p.then(result => {
      console.log(result)
    }).catch(error => {
      console.log(error)
    })
  </script>
Promise的三种状态

作用:了解Promise对象如何关联的处理函数,以及代码执行顺序

使用Promise + XHR获取省份列表
  <p class="my-p"></p>
  <script>
    /**
     * 目标:使用Promise管理XHR请求省份列表
     *  1. 创建Promise对象
     *  2. 执行XHR异步代码,获取省份列表
     *  3. 关联成功或失败函数,做后续处理
    */
    // 1. 创建Promise对象
    const p = new Promise((resolve, reject) => {
      // 2. 执行XHR异步代码,获取省份列表
      const xhr = new XMLHttpRequest()
      xhr.open('GET', 'http://hmajax.itheima.***/api/province')
      xhr.addEventListener('loadend', () => {
        // xhr如何判断响应成功还是失败的?
        // 2xx开头的都是成功响应状态码
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(JSON.parse(xhr.response))
        } else {
          reject(new Error(xhr.response))
        }
      })
      xhr.send()
    })

    // 3. 关联成功或失败函数,做后续处理
    p.then(result => {
      console.log(result)
      document.querySelector('.my-p').innerHTML = result.list.join('<br>')
    }).catch(error => {
      // 错误对象要用console.dir详细打印
      console.dir(error)
      // 服务器返回错误提示消息,插入到p标签显示
      document.querySelector('.my-p').innerHTML = error.message
    })

  </script>
封装简易axios函数 - 获取省份列表
  <p class="my-p"></p>
  <script>
    /**
     * 目标:封装_简易axios函数_获取省份列表
     *  1. 定义myAxios函数,接收配置对象,返回Promise对象
     *  2. 发起XHR请求,默认请求方法为GET
     *  3. 调用成功/失败的处理程序
     *  4. 使用myAxios函数,获取省份列表展示
    */
    // 1. 定义myAxios函数,接收配置对象,返回Promise对象
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        // 2. 发起XHR请求,默认请求方法为GET
        const xhr = new XMLHttpRequest()
        xhr.open(config.method || 'GET', config.url)
        xhr.addEventListener('loadend', () => {
          // 3. 调用成功/失败的处理程序
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        xhr.send()
      })
    }

    // 4. 使用myAxios函数,获取省份列表展示
    myAxios({
      url: 'http://hmajax.itheima.***/api/province'
    }).then(result => {
      console.log(result)
      document.querySelector('.my-p').innerHTML = result.list.join('<br>')
    }).catch(error => {
      console.log(error)
      document.querySelector('.my-p').innerHTML = error.message
    })
  </script>
封装简易axios函数 - 获取地区列表
  <p class="my-p"></p>
  <script>
    /**
     * 目标:封装_简易axios函数_获取地区列表
     *  1. 判断有params选项,携带查询参数
     *  2. 使用URLSearchParams转换,并携带到url上
     *  3. 使用myAxios函数,获取地区列表
    */
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        // 1. 判断有params选项,携带查询参数
        if (config.params) {
          // 2. 使用URLSearchParams转换,并携带到url上
          const paramsObj = new URLSearchParams(config.params)
          const queryString = paramsObj.toString()
          // 把查询参数字符串,拼接在url?后面
          config.url += `?${queryString}`
        }

        xhr.open(config.method || 'GET', config.url)
        xhr.addEventListener('loadend', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        xhr.send()
      })
    }

    // 3. 使用myAxios函数,获取地区列表
    myAxios({
      url: 'http://hmajax.itheima.***/api/area',
      params: {
        pname: '辽宁省',
        ***ame: '大连市'
      }
    }).then(result => {
      console.log(result)
      document.querySelector('.my-p').innerHTML = result.list.join('<br>')
    })

  </script>
封装简易axios函数 - 注册用户
  <button class="reg-btn">注册用户</button>
  <script>
    /**
     * 目标:封装_简易axios函数_注册用户
     *  1. 判断有data选项,携带请求体
     *  2. 转换数据类型,在send中发送
     *  3. 使用myAxios函数,完成注册用户
    */
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()

        if (config.params) {
          const paramsObj = new URLSearchParams(config.params)
          const queryString = paramsObj.toString()
          config.url += `?${queryString}`
        }
        xhr.open(config.method || 'GET', config.url)

        xhr.addEventListener('loadend', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        // 1. 判断有data选项,携带请求体
        if (config.data) {
          // 2. 转换数据类型,在send中发送
          const jsonStr = JSON.stringify(config.data)
          xhr.setRequestHeader('Content-Type', 'application/json')
          xhr.send(jsonStr)
        } else {
          // 如果没有请求体数据,正常的发起请求
          xhr.send()
        }
      })
    }

    document.querySelector('.reg-btn').addEventListener('click', () => {
      // 3. 使用myAxios函数,完成注册用户
      myAxios({
        url: 'http://hmajax.itheima.***/api/register',
        method: 'POST',
        data: {
          username: 'itheima999',
          password: '666666'
        }
      }).then(result => {
        console.log(result)
      }).catch(error => {
        console.dir(error)
      })
    })
  </script>

4、案例:天气预报

js部分

/**
 * 目标1:默认显示-北京市天气
 *  1.1 获取北京市天气数据
 *  1.2 数据展示到页面
 */
// 获取天气数据
function getWeather(cityCode) {
  myAxios({
    url: 'http://hmajax.itheima.***/api/weather',
    params: {
      city: cityCode,

    }
  }).then(result => {
    console.log(result)
    const wObj = result.data
    // 从上到下 从左到右展示
    // 当日日期
    document.querySelector('.title').innerHTML = `<span class="dateShort">${wObj.date}</span>
    <span class="calendar">农历&nbsp;
      <span class="dateLunar">${wObj.dateLunar}</span>
    </span>`
    // 城市名
    document.querySelector('.area').innerHTML = wObj.area
    // 当日气温
    document.querySelector('.weather-box').innerHTML = `<div class="tem-box">
        <span class="temp">
          <span class="temperature">${wObj.temperature}</span>
          <span>°</span>
        </span>
      </div>
      <div class="climate-box">
        <div class="air">
          <span class="psPm25">${wObj.psPm25}</span>
          <span class="psPm25Level">${wObj.psPm25Level}</span>
        </div>
        <ul class="weather-list">
          <li>
            <img src="${wObj.weatherImg}" class="weatherImg" alt="">
            <span class="weather">${wObj.weather}</span>
          </li>
          <li class="windDirection">${wObj.windDirection}</li>
          <li class="windPower">${wObj.windPower}</li>
        </ul>
      </div>`
    // 当日天气
    const twObj = wObj.todayWeather
    document.querySelector('.today-weather').innerHTML = `<div class="range-box">
        <span>今天:</span>
        <span class="range">
          <span class="weather">${wObj.weather}</span>
          <span class="temNight">${twObj.temNight}</span>
          <span>-</span>
          <span class="temDay">${twObj.temDay}</span>
          <span>℃</span>
        </span>
      </div>
      <ul class="sun-list">
        <li>
          <span>紫外线</span>
          <span class="ultraviolet">${twObj.ultraviolet}</span>
        </li>
        <li>
          <span>湿度</span>
          <span class="humidity">${twObj.humidity}</span>%
        </li>
        <li>
          <span>日出</span>
          <span class="sunriseTime">${twObj.sunriseTime}</span>
        </li>
        <li>
          <span>日落</span>
          <span class="sunsetTime">${twObj.sunsetTime}</span>
        </li>
      </ul>`
    // 七日内天气预报
    const dayForecast = wObj.dayForecast
    const dayForecastStr = dayForecast.map(item => {
      return `<li class="item">
      <div class="date-box">
        <span class="dateFormat">${item.dateFormat}</span>
        <span class="date">${item.date}</span>
      </div>
      <img src="${item.weatherImg}" alt="" class="weatherImg">
      <span class="weather">${item.weather}</span>
      <div class="temp">
        <span class="temNight">${item.temNight}</span>-
        <span class="temDay">${item.temDay}</span>
        <span>℃</span>
      </div>
      <div class="wind">
        <span class="windDirection">${item.windDirection}</span>
        <span class="windPower">&lt;${item.windPower}</span>
      </div>
    </li>`
    }).join('')
    document.querySelector('.week-wrap').innerHTML = dayForecastStr
  })
}
// 默认进入网页 - 就要获取天气数据 ( 北京:110100 )
getWeather('110100')

/*
搜索城市列表
1. 绑定input事件,获取关键字
2. 获取展示城市列表数据
*/
document.querySelector('.search-city').addEventListener('input', (e) => {
  // 获取input关键字 
  console.log(e.target.value)
  // 获取展示城市列表数据
  myAxios({
    url: 'http://hmajax.itheima.***/api/weather/city',
    params: {
      city: e.target.value
    }
  }).then(result => {
    console.log(result)
    const liStr = result.data.map(item => {
      return `<li class="city-item" data-code="${item.code}">${item.name}</li>`
    }).join('')
    document.querySelector('.search-list').innerHTML = liStr
  })


})

/*
展示城市天气 - 展示用户搜索查看的城市天气
1. 检测搜索列表点击事件,获取城市的code值
2. 调用获取并展示天气的函数
*/
document.querySelector('.search-list').addEventListener('click', e => {
  if (e.target.classList.contains('city-item')) {
    const cityCode = e.target.dataset.code
    console.log(cityCode);

    getWeather(cityCode)
  }
})

5、同步代码和异步代码

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

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

6、回调函数地狱

在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱

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

promise链式调用
  <script>
    /**
     * 目标:掌握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)
  </script>
解决回调函数地狱

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

    <span>省份:</span>
    <select>
      <option class="province"></option>
    </select>
    <span>城市:</span>
    <select>
      <option class="city"></option>
    </select>
    <span>地区:</span>
    <select>
      <option class="area"></option>
    </select>
  </form>
  <script src="https://cdn.jsdelivr.***/npm/axios/dist/axios.min.js"></script>
  <script>
    /**
     * 目标:把回调函数嵌套代码,改成Promise链式调用结构
     * 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
    */
    let pname = ''
    // 1. 得到-获取省份Promise对象
    axios({ url: 'http://hmajax.itheima.***/api/province' }).then(result => {
      pname = result.data.list[0]
      document.querySelector('.province').innerHTML = pname
      // 2. 得到-获取城市Promise对象
      return axios({ url: 'http://hmajax.itheima.***/api/city', params: { pname } })
    }).then(result => {
      const ***ame = result.data.list[0]
      document.querySelector('.city').innerHTML = ***ame
      // 3. 得到-获取地区Promise对象
      return axios({ url: 'http://hmajax.itheima.***/api/area', params: { pname, ***ame } })
    }).then(result => {
      console.log(result)
      const areaName = result.data.list[0]
      document.querySelector('.area').innerHTML = areaName
    })
  </script>
async函数和await - 解决回调函数调用

这两个关键字被称为JS中异步编程的终极解决方案

  <form>
    <span>省份:</span>
    <select>
      <option class="province"></option>
    </select>
    <span>城市:</span>
    <select>
      <option class="city"></option>
    </select>
    <span>地区:</span>
    <select>
      <option class="area"></option>
    </select>
  </form>
  <script src="https://cdn.jsdelivr.***/npm/axios/dist/axios.min.js"></script>
  <script>
    /**
     * 目标:掌握async和await语法,解决回调函数地狱
     * 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
     * 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
    */
    // 1. 定义async修饰函数
    async function getData() {
      // 2. await等待Promise对象成功的结果
      const pObj = await axios({ url: 'http://hmajax.itheima.***/api/province' })
      const pname = pObj.data.list[0]
      const cObj = await axios({ url: 'http://hmajax.itheima.***/api/city', params: { pname } })
      const ***ame = cObj.data.list[0]
      const aObj = await axios({ url: 'http://hmajax.itheima.***/api/area', params: { pname, ***ame } })
      const areaName = aObj.data.list[0]


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

    getData()
  </script>
async函数和await - 错误捕获
  <form>
    <span>省份:</span>
    <select>
      <option class="province"></option>
    </select>
    <span>城市:</span>
    <select>
      <option class="city"></option>
    </select>
    <span>地区:</span>
    <select>
      <option class="area"></option>
    </select>
  </form>
  <script src="https://cdn.jsdelivr.***/npm/axios/dist/axios.min.js"></script>
  <script>
    /**
     * 目标:async和await_错误捕获
    */
    async function getData() {
      // 1. try包裹可能产生错误的代码
      try {
        const pObj = await axios({ url: 'http://hmajax.itheima.***/api/province' })
        const pname = pObj.data.list[0]
        const cObj = await axios({ url: 'http://hmajax.itheima.***/api/city', params: { pname } })
        const ***ame = cObj.data.list[0]
        const aObj = await axios({ url: 'http://hmajax.itheima.***/api/area', params: { pname, ***ame } })
        const areaName = aObj.data.list[0]

        document.querySelector('.province').innerHTML = pname
        document.querySelector('.city').innerHTML = ***ame
        document.querySelector('.area').innerHTML = areaName
      } catch (error) {
        // 2. 接着调用catch块,接收错误信息
        // 如果try里某行代码报错后,try中剩余的代码不会执行了
        console.dir(error)
      }
    }

    getData()
  </script>

7、事件循环

这两个代码结果都是1、3、2

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

        浏览器是多线程的,任务队列是内存里面开辟的空间。

  <script>
    /**
     * 目标:阅读并回答执行的顺序结果
    */
    console.log(1)
    setTimeout(() => {
      console.log(2)
    }, 0)
    console.log(3)
    setTimeout(() => {
      console.log(4)
    }, 2000)
    console.log(5)
    
  </script>

        代码运行时,读到第一句会把它放进调用栈,调用执行,在控制台打印,执行后出栈;再读第二段代码,发现是异步代码,然后交给宿主环境执行,然后浏览器开始倒计时,发现是0s,然后会立即把要执行的回调函数的消息推入任务队列中进行排队,这就是为什么0s后没有立刻执行的原因;然后执行下一段,控制台打印,然后出栈;执行下一段,发现是异步,然后放入宿主环境;

执行下一段,控制台打印,出栈;同步代码执行完,调用栈空闲,当调用栈空闲时,会一直尝试到任务队列中调用要执行的回调函数,将它推入调用栈,控制台打印,出栈,2s的倒计时结束后,会推入任务队列中,然后调用栈调用......

        因此上述代码打印结果为:1、3、5、2、4

练习:

  <script>
    /**
     * 目标:阅读并回答执行的顺序结果
    */
    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.***/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
  </script>

按上面的方式,这段代码打印结果为:1、5、3、2、4   点击一次document就执行一次打印6

8、宏任务和微任务

        script标签JS脚本执行事件会交给浏览器,然后整个js脚本代码推入宏任务队列中,调用栈是空闲的,然后立即执行第一句代码,控制台打印1;然后发现下一段代码是异步任务,是宏任务,执行所在环境是浏览器,因此交给浏览器,浏览器倒计时发现是0s,然后将这个回调函数放入宏任务队列中排队,然后js引擎继续执行代码,然后放入调用栈进行调用,promise本身是同步,因此控制台打印3,然后resolve这行代码标记当前promise成功状态;继续往下,.then是同步的,因此调用栈调用,但是里面的回调函数是异步的,因此这个异步回调函数会推入微任务队列,出栈(调用栈);打印5,调用栈空闲,反复调用任务队列,优先调度微任务,因为微任务更接近js引擎,打印4,直到微任务队列全部执行完毕,然后执行宏任务队列......最终控制台打印:1、3、5、4、2

练习:

  <script>
    // 目标:回答代码执行顺序
    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任务状态改变就会调用,推入微任务中排队
    p.then(result => console.log(result))
    const p2 = new Promise(resolve => resolve(6))
    // 当p任务状态改变就会调用,推入微任务中排队
    p2.then(result => console.log(result))
    console.log(7)
    // 1 7 5 6 2 3 4
  </script>

9、Promise.all静态方法

构造函数. 的方法都叫静态方法

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

10、token介绍

const token = localStorage.getItem('token')
if (!token) {
  // 无 token 令牌字符串,没有登录状态,则强制跳转到登录页
  location.href = '../login/index.html'
}

11、axios请求拦截器和响应拦截器

个人信息设置和axios请求拦截器

axios请求拦截器:发起请求之前,触发的配置函数,对请求参数进行额外配置

        use方法调用时,传递两个函数体,第一个函数体是请求拦截器要对本次请求的相关参数的配置对象可以进行一些额外的设置;而第二个函数体在请求发起之前,有什么错误信息,做一些处理,应用场景较少,可以不写。

        在项目中,使用相同的axios函数发起请求时,如请求个人信息、所有频道、文章列表,都会经历axios请求拦截器的处理,都会在本次请求的请求头上携带token字符串,然后到达服务器端来表明身份,服务器发现token合法就会返回相应的数据到我们请求的位置进行使用。

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  // 统一携带 token 令牌字符串在请求头上
  const token = localStorage.getItem('token')
  token && (config.headers.Authorization = `Bearer ${token}`)

  return config
}), function (error) {
  // 对请求错误做什么
  return Promise.reject(error)
}
/**
 * 目标2:设置个人信息
 * 2.1 在 utils/request.js 设置请求拦截器,统一携带 token
 * 2.2 请求个人信息并设置到页面
 */
axios({
  url: '/v1_0/user/profile'
}).then(result => {
  console.log(result)
  const username = result.data.data.name
  document.querySelector('.nick-name').innerHTML = username
})
axios响应拦截器和身份验证失败

        axios响应拦截器:响应回到then/catch之前,触发的拦截函数,对响应结果统一处理

        服务器在响应回结果到达具体逻辑页面的axios的then/catch之前,会先经历axios响应拦截器函数的拦截,在响应拦截器函数里面,我们可以对服务器返回的结果进行一些统一的处理,或者对本次响应错误结果的对象做出一些处理,处理之后再统一返回到具体的逻辑页面中,因此respond的结果会返回到then的result参数上,而error错误信息的对象会被作为promise失败状态的值,会影响到axios函数留在原地的那个promise对象的状态,error就传给catch的error形参上,因此axios响应拦截器就是统一对响应的结果进行拦截,然后做出统一的处理后,再回到具体的逻辑页面中。

        ?. : 是可选链运算符,允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。

        如果不写?,那么假设error对象中没有data属性,那么在获取status时,代码容易报错 undefined

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
  return response;
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么 eg: 统一对 401 身份验证失败情况做出处理
  // 要等1个小时,token 才过期 前端判断 token 的有无,后端判断有效性
  console.dir(error)
  // 如果不写?,那么假设error对象中没有data属性,那么在获取status时,代码容易报错 undefined
  // ?. : 是可选链运算符,允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。
  if (error?.response?.status === 401) {
    alert('身份验证失败,请重新登录')
    // token 过期,清空本地存储
    localStorage.clear()
    // 强制弹回登录界面
    location.href = '../login/index.html'
  }
  return Promise.reject(error);
});
优化 - axios响应结果

目标:axios直接接收服务器返回的响应结果

        在逻辑页面发起的这些axios请求,让result直接就是服务器返回的数据对象

注:代码写在黑马头条案例中。

转载请说明出处内容投诉
CSS教程网 » Ajax(二):Bootstrap、XHR、Promise、同步、异步代码、回调函数地狱、事件循环、宏任务微任务、Promise.all方法、token、axios请求拦截器和响应拦截器

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买