AJAX
1. AJAX入门
1.1 AJAX 概念和 axios 使用
1.1.1 什么是AJAX
AJAX是异步的JavaScript
和XML
(Asynchronous JavaScript And XML
)。简单点说,就是使用XMLHttpRequest
对象与服务器通信。它可以使用JSON
,XML
,HTML
和text
文本等格式发送和接收数据。AJAX最吸引人的就是它的”异步“特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。
例如:
使用浏览器的 XMLHttpRequest 对象 与服务器通信
浏览器网页中,使用 AJAX技术(XHR对象)发起获取省份列表数据的请求,服务器代码响应准备好的省份列表数据给前端,前端拿到数据数组以后,展示到网页
所以,AJAX是浏览器与服务器进行数据通信的技术。
1.1.2 怎么用
先使用
axios[ek'sious]
库,与服务器进行数据通信基于
XMLHttpRequest
封装、代码简单、月下载量在14亿次。Vue、React项目中都会用到axios
再学习
XMLHttpRequest
对象的使用,了解AJAX底层原理
使用步骤:
引入 axios.js 文件到自己的网页中: https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
使用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
什么是 URL ?
统一资源定位符,简称网址,用于定位网络中的资源(资源指的是:网页,图片,数据,视频,音频等等)
URL 的组成?
协议,域名,资源路径(URL 组成有很多部分,我们先掌握这3个重要的部分即可)
什么是 http 协议 ?
又称为超文本传输协议,规定了浏览器和服务器传递数据的格式(而格式具体有哪些稍后我们就会学到)
什么是域名 ?
标记服务器在互联网当中的方位,网络中有很多服务器,你想访问哪一台,就需要知道它的域名才可以
什么是资源路径 ?
一个服务器内有多个资源,用于标识你要访问的资源具体的位置
案例:接下来做个需求,访问新闻列表的 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 查询参数
什么是查询参数 ?
携带给服务器额外信息,让服务器返回我想要的某一部分数据而不是全部数据
举例:查询河北省下属的城市列表,需要先把河北省传递给服务器
查询参数的语法 ?
在 url 网址后面用
?
拼接格式:http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2
参数名一般是后端规定的,值前端看情况传递即可
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协议规定的),每个单词对应一种对服务器资源要执行的操作
前面我们获取数据其实用的就是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阶段接口文档
例如:获取城市列表接口样子
需求:打开 AJAX 阶段接口文档,查看登录接口,并编写代码,完成一次登录的效果吧
代码如下:
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 内部与服务器交互过程的真正原理
使用步骤:
创建XMLHttpRequest对象
const xhr = new XMLHttpRequest()
配置请求方法和url地址
xhr.open('请求方法', '请求url网址')
监听loaded时间,接收响应结果
xhr.addEventListener('loadend', () => { // 响应结果 console.log(xhr.response) })
发起请求
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 - 数据提交
在提交数据的时候注意以下几点:
没有 axios 帮我们了,我们需要自己设置请求头 Content-Type:application/json,来告诉服务器端,我们发过去的内容类型是 JSON 字符串,让他转成对应数据结构取值使用
没有 axios 了,我们前端要传递的请求体数据,也没人帮我把 JS 对象转成 JSON 字符串了,需要我们自己转换
原生 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 的好处是什么:
逻辑更清晰(成功或失败会关联后续的处理函数)
了解 axios 函数内部运作的机制
能解决回调函数地狱问题(后面会讲到),今天先来看下它的基础使用
语法:
// 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 对象必定处于以下三种状态之一
- 待定(
pending
):初始状态,既没有被兑现,也没有被拒绝 - 已兑现(
fulfilled
):操作成功完成 - 已拒绝(
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 => {
})
步骤:
- 定义 myAxios 函数,接收配置对象,返回 Promise 对象
- 发起 XHR 请求,默认请求方法为 GET
- 调用成功/失败的处理程序
- 使用 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 函数基础上,修改代码支持传递查询参数功能
修改步骤:
- myAxios 函数调用后,判断 params 选项
- 基于 URLSearchParams 转换查询参数字符串
- 使用自己封装的 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 函数支持传递请求体数据,完成注册用户功能
修改步骤:
- myAxios 函数调用后,判断 data 选项
- 转换数据类型,在 send 方法中发送
- 使用自己封装的 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 同步代码和异步代码
同步代码:逐行执行,需原地等待结果后,才继续向下执行
异步代码:调用后耗时,不阻塞代码继续执行(不必原地等待),在将来完成后触发回调函数传递结果
回答代码打印顺序:发现异步代码接收结果,使用的都是回调函数
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链式调用
概念:依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束
细节:then() 回调函数中的返回值,会影响新生成的 Promise 对象最终状态和结果
好处:通过链式调用,解决回调函数嵌套问题
形式上可以理解为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>
使用场景:当需要同时渲染多个接口数据同时到网页上时使用