AJAX笔记


AJAX

1. AJAX入门

1.1 AJAX 概念和 axios 使用

1.1.1 什么是AJAX

AJAX是异步JavaScriptXML(Asynchronous JavaScript And XML)。简单点说,就是使用XMLHttpRequest对象与服务器通信。它可以使用JSONXMLHTMLtext文本等格式发送和接收数据。AJAX最吸引人的就是它的”异步“特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面

例如:

  • 使用浏览器的 XMLHttpRequest 对象 与服务器通信

  • 浏览器网页中,使用 AJAX技术(XHR对象)发起获取省份列表数据的请求,服务器代码响应准备好的省份列表数据给前端,前端拿到数据数组以后,展示到网页

所以,AJAX是浏览器与服务器进行数据通信的技术。

1.1.2 怎么用

  • 先使用axios[ek'sious]库,与服务器进行数据通信

    基于XMLHttpRequest封装、代码简单、月下载量在14亿次。

    Vue、React项目中都会用到axios

  • 再学习XMLHttpRequest对象的使用,了解AJAX底层原理

使用步骤:

  1. 引入 axios.js 文件到自己的网页中: https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js

  2. 使用axios函数:

    axios({
      url: '目标资源地址'
    }).then((result) => {
      // 对服务器返回的数据做后续处理
    })
    

    注意:请求的 url 地址, 就是标记资源的网址

    注意:then 方法这里先体验使用,由来后续会讲到

案例:从服务器获取省份列表数据,展示到页面上(体验 axios 语法的使用)

获取省份列表数据 - 目标资源地址:http://hmajax.itheima.net/api/province

<!--首先引入axios的库-->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!--其次再使用axios-->
<script>
    axios({
        url: 'http://hmajax.itheima.net/api/province'
    }).then(res => {
        console.log(res);
        console.log(res.data);
        console.log(res.data.list);
        document.write(res.data.list.join('<br>'))
    })
</script>

运行结果

1.2 认识 URL

  1. 什么是 URL ?

    统一资源定位符,简称网址,用于定位网络中的资源(资源指的是:网页,图片,数据,视频,音频等等)

  2. URL 的组成?

    协议,域名,资源路径(URL 组成有很多部分,我们先掌握这3个重要的部分即可)

    url组成

  3. 什么是 http 协议 ?

    又称为超文本传输协议,规定了浏览器和服务器传递数据的格式(而格式具体有哪些稍后我们就会学到)

  4. 什么是域名 ?

    标记服务器在互联网当中的方位,网络中有很多服务器,你想访问哪一台,就需要知道它的域名才可以

  5. 什么是资源路径 ?

    一个服务器内有多个资源,用于标识你要访问的资源具体的位置

案例:接下来做个需求,访问新闻列表的 URL 网址,打印新闻数据

新闻列表数据 URL 网址:http://hmajax.itheima.net/api/news

<!--首先引入axios的库-->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!--其次再使用axios-->
<script>
    axios({
        url: 'http://hmajax.itheima.net/api/news'
    }).then(res => {
        console.log(res);
        console.log(res.data);
    })
</script>

url解释:使用http协议访问域名hmajax.itheima.net下的/api/news资源

执行结果

1.3 URL 查询参数

  1. 什么是查询参数 ?

    携带给服务器额外信息,让服务器返回我想要的某一部分数据而不是全部数据

    举例:查询河北省下属的城市列表,需要先把河北省传递给服务器

  2. 查询参数的语法 ?

    在 url 网址后面用?拼接格式:http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2

    参数名一般是后端规定的,值前端看情况传递即可

  3. axios 如何携带查询参数?

    使用 params 选项即可

    axios({
      url: '目标资源地址',
      params: {
        参数名:}
    }).then(result => {
      // 对服务器返回的数据做后续处理
    })
    

案例:获取“河北省”下属的城市列表,展示到页面,对应代码:

查询城市列表的 url地址:http://hmajax.itheima.net/api/city

<!--首先引入axios的库-->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!--其次再使用axios-->
<script>
    axios({
        url: 'http://hmajax.itheima.net/api/city'
    }).then(res => {
        console.log(res);
    })
</script>

结果

因为我们没有携带参数来查询,所以是查询不了数据的,现在根据message的提示来加上参数再查询一次:

<!--首先引入axios的库-->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!--其次再使用axios-->
<script>
    axios({
        url: 'http://hmajax.itheima.net/api/city',
        params:{
            pname: '重庆'
        }
    }).then(res => {
        console.log(res);
    })
</script>

可见查询成功

注意:后面为了简化代码,我不会再贴出引入axios库的过程了。

1.4 案例-查询-地区列表

需求:根据输入的省份名字和城市名字,查询下属地区列表

完成效果如下:

效果图

相关参数

查询地区: http://hmajax.itheima.net/api/area

参数名:

pname:省份名字

cname:城市名字

代码如下:

<label for="province">省份名字</label>
<input id="province" type="text" class="province">
<label for="city">城市名字</label>
<input id="city" type="text" class="city">
<button class="sel-btn">查询</button>
<ul class="list-group"></ul>
<!--首先引入axios的库-->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!--其次再使用axios-->
<script>
    /*
      获取地区列表: http://hmajax.itheima.net/api/area
      查询参数:
        pname: 省份或直辖市名字
        cname: 城市名字
    */
    // 目标: 根据省份和城市名字, 查询地区列表
    // 1. 查询按钮-点击事件
    document.querySelector('.sel-btn').addEventListener('click', () => {
        // 2. 获取省份和城市名字
        const pname = document.querySelector('.province').value
        const cname = document.querySelector('.city').value

        // 3. 基于axios请求地区列表数据
        axios({
            url: 'http://hmajax.itheima.net/api/area',
            params: {
                pname: pname, //这里对于ES6的语法来说可以简写 - 当属性名和value位置变量名同名即可简写
                cname: cname
                // pname, //简写形式
                // cname
            }
        }).then(result => {
            // console.log(result)
            // 4. 把数据转li标签插入到页面上
            let list = result.data.list
            console.log(list)
            let theLi = list.map(areaName => `<li class="list-group-item">${areaName}</li>`).join('')
            console.log(theLi)
            document.querySelector('.list-group').innerHTML = theLi
        })
    })
</script>

执行结果

1.5 常用请求方法和数据提交

想要提交数据,先来了解什么是请求方法

  • 请求方法是一些固定单词的英文,例如:GET,POST,PUT,DELETE,PATCH(这些都是http协议规定的),每个单词对应一种对服务器资源要执行的操作

    image-20230404104319428

  • 前面我们获取数据其实用的就是GET请求方法,但是axios内部设置了默认请求方法就是GET,我们就没有写

  • 但是提交数据需要使用POST请求方法

什么时候进行数据提交呢?

  • 例如:多端要查看同一份订单数据,或者使用同一个账号进行登录,那订单/用户名+密码,就需要保存在服务器上,随时随地进行访问

axios 如何提交数据到服务器呢?

需要学习,method data 这2个新的选项了(大家不用担心,这2个学完,axios常用的选项就都学完了)

axios({
  url: '目标资源地址',
  method: '请求方法',
  data: {
    参数名:}
}).then(result => {
  // 对服务器返回的数据做后续处理
})

其中data是我们需要提交的数据,method是提交的方法。

案例:需求:注册账号,提交用户名和密码到服务器保存

注册用户 URL 网址:http://hmajax.itheima.net/api/register

请求方法:POST

参数名:

username:用户名(要求中英文和数字组成,最少8位)

password:密码(最少6位)

代码如下:

axios({
    url:'http://hmajax.itheima.net/api/register',
    method: 'POST',
    data:{
        username: 'nxz123456',
        password: '123456'
    }
}).then(res => {
    console.log(res)
})

结果:注册成功。

axios的核心配置:url:目标资源地址,method:请求方法,params:查询参数,data:提交的数据

1.6 axios 错误处理

如果注册相同的用户名,则会遇到注册失败的请求,也就是 axios 请求响应失败了,你会在控制台看到如图的错误:

在 axios 语法中要如何处理呢?

因为,普通用户不会去控制台里看错误信息,我们要编写代码拿到错误并展示给用户在页面上

所以,使用 axios 的 catch 方法,捕获这次请求响应的错误并做后续处理,语法如下:

axios({
  // ...请求选项
}).then(result => {
  // 处理成功数据
}).catch(error => {
  // 处理失败错误
})

需求:再次重复注册相同用户名,提示用户注册失败的原因

代码:

axios({
    url:'http://hmajax.itheima.net/api/register',
    method: 'POST',
    data:{
        username: 'nxz123456',
        password: '123456'
    }
}).then(res => {
    console.log(res)
}).catch(err =>{
    console.log(err)
    console.log(err.response.data.message)
})

1.7 HTTP 协议-请求报文

首先,HTTP 协议规定了浏览器和服务器返回内容的格式

请求报文:是浏览器按照协议规定发送给服务器的内容,例如刚刚注册用户时,发起的请求报文:

这里的格式包含:

  • 请求行:请求方法,URL,协议;对应上图:POST http://hmajax.itheima.net/api/register HTTP/1.1
  • 请求头:以键值对的格式携带的附加信息,比如:Content-Type(指定了本次传递的内容类型)
  • 空行:分割请求头,空行之后的是发送给服务器的资源
  • 请求体:发送的资源;对应上图:{"username":"itheima007,"password":"7654321"}

上面还有载荷:请求的参数;响应:服务器返回给浏览器的数据

1.9 HTTP 协议-响应报文

响应报文:是服务器按照协议固定的格式,返回给浏览器的内容

响应报文的组成:

  • 响应行(状态行):协议,HTTP响应状态码,状态信息
  • 响应头:以键值对的格式携带的附加信息,比如:Content-Type(告诉浏览器,本次返回的内容类型)
  • 空行:分割响应头,控制之后的是服务器返回的资源
  • 响应体:返回的资源

HTTP 响应状态码:

  • 用来表明请求是否成功完成

  • 例如:404(客户端要找的资源,在服务器上不存在)

1.10 接口文档

接口文档:描述接口的文章(一般是后端工程师,编写和提供)

接口:指的使用 AJAX 和 服务器通讯时,使用的 URL,请求方法,以及参数,例如:AJAX阶段接口文档

例如:获取城市列表接口样子

  1. 需求:打开 AJAX 阶段接口文档,查看登录接口,并编写代码,完成一次登录的效果吧

  2. 代码如下:

    document.querySelector('.btn').addEventListener('click', () => {
      // 用户登录
      axios({
        url: 'http://hmajax.itheima.net/api/login',
        method: 'post',
        data: {
          username: 'itheima007',
          password: '7654321'
        }
      })
    })
    

2. AJAX原理

2.1 XMLHttpRequest - 基础使用

什么是XMLHttpRequest XMLHttpRequest(XHR)对象用于与服务器交互。通过XMLHttpRequest可以在不刷新页面的情况下请求特定URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。MLHttpRequest在 AJAX编程中被大量使用。

关系:axios内部采用XMLHttpRequest与服务器进行交互

关系

那既然axios能与服务器进行交互,我们为什么还要学习XMLHttpRequest呢?因为axios 是对 XHR 相关代码进行了封装,让我们只关心传递的接口参数,在某些特定的场景下,比如一些静态网站,很少与服务器进行交互,考虑到项目体积的大小,这时我们就可以使用XMLHttpRequest来与服务器进行交互,而不考虑使用axios。同时学习 XHR 也是了解 axios 内部与服务器交互过程的真正原理

使用步骤:

  1. 创建XMLHttpRequest对象

    const xhr = new XMLHttpRequest()
    
  2. 配置请求方法和url地址

    xhr.open('请求方法', '请求url网址')
    
  3. 监听loaded时间,接收响应结果

    xhr.addEventListener('loadend', () => {
      // 响应结果
      console.log(xhr.response)
    })
    
  4. 发起请求

    xhr.send()
    

整体步骤

const xhr = new XMLHttpRequest()
xhr.open('请求方法', '请求url网址')
xhr.addEventListener('loadend', () => {
  // 响应结果
  console.log(xhr.response)
})
xhr.send()

案例:以一个需求来体验下原生 XHR 语法,获取所有省份列表并展示到页面上

<p class="my-p"></p>
<script>
    const xhr = new XMLHttpRequest();
    xhr.open('GET','http://hmajax.itheima.net/api/province');
    xhr.addEventListener('loadend',()=>{ //当加载结束之后才执行,相当于获取到请求的结果之后才执行的
        console.log(xhr.response)
        const data = JSON.parse(xhr.response)
        console.log(data.list.join('<br>'))
        document.querySelector('.my-p').innerHTML = data.list.join('<br>')
    })
    //这才是真正的发送请求
    xhr.send();
</script>

结果自己查看即可。

2.2 XMLHttpRequest - 查询参数

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

例如:查询河北省下属的城市列表

语法:http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2

const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://hmajax.itheima.net/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()

2.3 案例 - 地区查询

案例:不用 axios 而是用 XHR 实现,输入省份和城市名字后,点击查询,传递多对查询参数并获取地区列表的需求

但是多个查询参数,如果自己拼接很麻烦,这里用 URLSearchParams 把参数对象转成“参数名=值&参数名=值“格式的字符串,语法如下:

// 1. 创建 URLSearchParams 对象
const paramsObj = new URLSearchParams({
  参数名1:1,
  参数名2:2
})

// 2. 生成指定格式查询参数字符串
const queryString = paramsObj.toString()// 结果:参数名1=值1&参数名2=值2

2.4 XMLHttpRequest - 数据提交

在提交数据的时候注意以下几点:

  1. 没有 axios 帮我们了,我们需要自己设置请求头 Content-Type:application/json,来告诉服务器端,我们发过去的内容类型是 JSON 字符串,让他转成对应数据结构取值使用

  2. 没有 axios 了,我们前端要传递的请求体数据,也没人帮我把 JS 对象转成 JSON 字符串了,需要我们自己转换

  3. 原生 XHR 需要在 send 方法调用时,传入请求体携带

const xhr = new XMLHttpRequest()
xhr.open('请求方法', '请求url网址')
xhr.addEventListener('loadend', () => {
  console.log(xhr.response)
})

// 1. 告诉服务器,我传递的内容类型,是 JSON 字符串
xhr.setRequestHeader('Content-Type', 'application/json')
// 2. 准备数据并转成 JSON 字符串
const user = { username: 'itheima007', password: '7654321' }
const userStr = JSON.stringify(user)
// 3. 发送请求体数据
xhr.send(userStr)

2.5 认识Promise

什么是 Promise:Promise 对象用于表示一个异步操作的最终完成(或失败)及其结构值

Promise 的好处是什么:

  1. 逻辑更清晰(成功或失败会关联后续的处理函数)

  2. 了解 axios 函数内部运作的机制

    三者的关系

  3. 能解决回调函数地狱问题(后面会讲到),今天先来看下它的基础使用

语法:

// 1. 创建 Promise 对象
const p = new Promise((resolve, reject) => {
 // 2. 执行异步任务-并传递结果
 // 成功调用: resolve(值) 触发 then() 执行
 // 失败调用: reject(值) 触发 catch() 执行
})

// 3. 接收结果
p.then(result => {
 // 成功
}).catch(error => {
 // 失败
})

2.6 Promise的三种状态

为什么要了解 Promise 的三种状态 :知道 Promise 对象如何关联的处理函数,以及代码的执行顺序

Promise 有哪三种状态:每个 Promise 对象必定处于以下三种状态之一

  1. 待定(pending):初始状态,既没有被兑现,也没有被拒绝
  2. 已兑现(fulfilled):操作成功完成
  3. 已拒绝(rejected):操作失败

状态的英文字符串,可以理解为 Promise 对象内的字符串标识符,用于判断什么时候调用哪一个处理函数

Promise 的状态改变有什么用:调用对应函数,改变 Promise 对象状态后,内部触发对应回调函数传参并执行

注意:每个 Promise 对象一旦被兑现/拒绝,那就是已敲定了,状态无法再被改变

2.7 使用Promise和XHR获取省份列表

案例:使用 Promise 和 XHR 请求省份列表数据并展示到页面上

代码如下:

<p class="my-p"></p>
<script>
    const p = new Promise((resolve, reject)=>{
        const xhr = new XMLHttpRequest();
        xhr.open('GET','http://hmajax.itheima.net/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();
    });
    p.then(res=>{
        console.log(res);
        document.querySelector('.my-p').innerHTML = res.list.join('<br>');
    })
    p.catch(err=>{
        // 错误对象要用console.dir详细打印
        console.dir(err)
        // 服务器返回错误提示消息,插入到p标签显示
        document.querySelector('.my-p').innerHTML = err.message
    })
</script>

2.8 封装简易axios获取省份列表

为了更加明白axios的原理,这里自己手动封装一下。

案例:基于 Promise 和 XHR 封装 myAxios 函数,获取省份列表展示到页面

例如:

function myAxios(config) {
  return new Promise((resolve, reject) => {
    // XHR 请求
    // 调用成功/失败的处理程序
  })
}

myAxios({
  url: '目标资源地址'
}).then(result => {
    
}).catch(error => {
    
})

步骤:

  1. 定义 myAxios 函数,接收配置对象,返回 Promise 对象
  2. 发起 XHR 请求,默认请求方法为 GET
  3. 调用成功/失败的处理程序
  4. 使用 myAxios 函数,获取省份列表展示
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.net/api/province' //这里用于传递一个配置对象 -- config
}).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
})

结果自己查看即可。

2.9 封装简易axios获取地区列表

案例:在上个封装的建议 axios 函数基础上,修改代码支持传递查询参数功能

修改步骤:

  1. myAxios 函数调用后,判断 params 选项
  2. 基于 URLSearchParams 转换查询参数字符串
  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) //URLSearchParams会自动加上&符号
      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.net/api/area',
  params: {
    pname: '辽宁省',
    cname: '大连市'
  }
}).then(result => {
  console.log(result)
  document.querySelector('.my-p').innerHTML = result.list.join('<br>')
})

结果自行验证即可。

2.10 封装简易axios注册用户

案例:修改 myAxios 函数支持传递请求体数据,完成注册用户功能

修改步骤:

  1. myAxios 函数调用后,判断 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') //如果有请求体,需要将请求头变为json形式,然后传递json字符串
      xhr.send(jsonStr)
    } else {
      // 如果没有请求体数据,正常的发起请求
      xhr.send()
    }
  })
}

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

此部分的XMLHttpRequest和Promise要好好学习,在实际开发中,才会自己封装请求,但是大多数还是直接使用axios。

3. AJAX进阶

3.1 同步代码和异步代码

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

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

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

    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)
    

    执行结果:如果在2秒钟内点击了btn按钮,执行结果为1432,反之为1423。

3.2 回调函数地狱

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

例如:现在我有一个需求需求,默认第一个省,第一个城市,第一个地区在下拉菜单中

我们代码可能如下:

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]
    })
  })
})

可见,我们的axios里面,执行成功的回调then中,有嵌套了axios,这种回调函数中嵌套回调函数的情况,就称为回调函数地狱。

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

为了解决回调函数地狱问题,我们引入了Promise链式调用。

3.3 Promise链式调用

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

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

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

形式上可以理解为Java中的链式调用

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

/**
 * 目标:掌握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)

执行结果

then 回调函数中,return 的值会传给 then 方法生成的新 Promise 对象

3.4 Promise链式调用解决回调地狱

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

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

/**
 * 目标:把回调函数嵌套代码,改成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
})

从axios上看就更加形象了。

注意:如果这里没明白为什么axios中return了一个axios后面可以继续跟上then的伙伴,你可以去仔细看看AJAX原理一部分,看看是怎么使用Promise来封装axios的。

3.5 async函数和await

async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。简而言之就是使用这两个关键字来代替Promise繁琐的链式调用。

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

做法:使用 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必须用在async修饰的函数内(await会阻止”异步函数内”代码继续执行,原地等待结果)

3.6 async函数和await捕获错误

有基础的伙伴都知道,我们捕获程序的异常一般都是使用try-catch来完成:

try {
  // 要执行的代码
} catch (error) {
  // error 接收的是,错误消息
  // try 里代码,如果有错误,直接进入这里执行
}

在async和await中,try 和 catch 有什么作用:捕获同步流程的代码报错信息

例如:尝试把代码中 url 地址写错,运行观察 try catch 的捕获错误信息能力

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()

3.7 事件循环

注意:时间循环是在JavaScript基础中总结过的,你可以去JavaScript基础中看看。

简单总结一下:

什么是事件循环:执行代码和收集异步任务,在调用栈空闲时,反复调用任务队列里回调函数执行机制

为什么有事件循环:JavaScript 是单线程的,为了不阻塞 JS 引擎,设计执行代码的模型

JavaScript 内代码如何执行:执行同步代码,遇到异步代码交给宿主浏览器环境执行,异步有了结果后,把回调函数放入任务队列排队,当调用栈空闲后,反复调用任务队列里的回调函数

注意:如果事件循环看文章描述看不懂,推荐直接看B站黑马程序员中:黑马程序员前端AJAX入门到实战全套教程,包含学前端框架必会的(ajax+node.js+webpack+git),一套全覆盖中第53个视频。

3.8 宏任务与微任务

注意:如果宏任务和微任务看文章描述看不懂,推荐直接看B站黑马程序员中:黑马程序员前端AJAX入门到实战全套教程,包含学前端框架必会的(ajax+node.js+webpack+git),一套全覆盖中第55个视频。

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

异步任务划分为了:

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

宏任务和微任务具体划分:

例如:

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)

执行过程分析

调用栈由于是单线程执行的是同步代码,而宿主环境由于是多线程的是异步代码。

注意:宏任务每次在执行同步代码时,产生微任务队列,清空微任务队列任务后,微任务队列空间释放!下一次宏任务执行时,遇到微任务代码,才会再次申请微任务队列空间放入回调函数消息排队

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

3.9 Promise.all静态方法

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

语法:

const p = Promise.all([Promise对象, Promise对象, ...])
p.then(result => {
  // result 结果: [Promise对象成功结果, Promise对象成功结果, ...]
}).catch(error => {
  // 第一个失败的 Promise 对象,抛出的异常对象
})

案例:需求:同时请求“北京”,“上海”,“广州”,“深圳”的天气并在网页尽可能同时显示

核心代码:

<!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>

使用场景:当需要同时渲染多个接口数据同时到网页上时使用


文章作者: 念心卓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 念心卓 !
  目录