十三.Promise的静态方法all、race、resolve和reject
3.Promise的静态方法resolve和reject方法
一.XMLHttpRequest
使用格式
// 1. 创建一个xhr对象
let xhr = new XMLHttpRequest()
// 2. 设置请求方式和请求地址
xhr.open(请求方式, 请求地址)
// 3. 发送请求
xhr.send()
// 4. 监听load(请求成功)事件 ==> 可以获取响应数据
xhr.addEventListener('load', function() {
console.log(xhr.response) // 服务器返回的数据
})
例:
// 请求省份名字列表
// 接口地址: http://ajax-api.itheima.net/api/province
// 请求方式:GET
// 原生Ajax编写↓
// 1.创建一个xhr对象
let xhr = new XMLHttpRequest()
// 2.设置请求方式和请求地址 xhr.open("请求方式", "请求地址")
xhr.open('GET', 'http://ajax-api.itheima.net/api/province')
// 3.发送请求
xhr.send()
// 4.监听load(请求成功)事件 ==> 可以获取响应数据
xhr.addEventListener('load', function () {
console.log(xhr.response) // 响应体数据
// 知识点: 把JSON格式字符串 ==> 转成JS数据类型
// 语法: JSON.parse(JSON字符串)
// 返回值: JS数据类型
let result = JSON.parse(xhr.response)
console.log(result);
})
Ajax是一个技术的名称, 而以前我们具体使用的是axios.js
插件来发送请求 ,但是其实axios.js也是一个库, 内部是原生JS代码, 实际它是对原生的代码进行了封装。
1.代码运行流程
Ajax原理底层依靠一个构造函数那就是XMLHttpRequest
二.Ajax的原生query传参
// 目标: 请求辽宁省下所有城市列表
let xhr = new XMLHttpRequest()
// 复习: 之前说过query查询参数要携带在url?后面
// 格式: 参数名=值&参数名=值
xhr.open('GET', `http://ajax-api.itheima.net/api/city?pname=辽宁省`)
xhr.send()
xhr.addEventListener('load', function () {
let result = JSON.parse(xhr.response)
console.log(result);
})
原生Ajax传递查询参数在url后面拼接查询参数
三.Ajax的原生POST
使用格式
// 1. 创建一个xhr对象
let xhr = new XMLHttpRequest()
// 2. 设置请求方式和请求地址
xhr.open(请求方式, 请求地址)
// 3. 发送请求
// 请求体
xhr.send(请求体)
// 4. 监听load(请求成功)事件 ==> 可以获取响应数据
xhr.addEventListener('load', function() {
console.log(xhr.response) // 服务器返回的数据
})
实例完整代码
// 目标: 测试POST请求
// 接口地址: http://ajax-api.itheima.net/api/books
// 请求方式: POST
// 参数随意
let xhr = new XMLHttpRequest()
xhr.open('POST', `http://ajax-api.itheima.net/api/books`)
// 2. 需要携带请求头Content-Type和固定值指定传递的请求体内容类型, 让后端用对应方式解析
// 要注意: 在open后send前之间设置
xhr.setRequestHeader('Content-Type', 'application/json')
// 1. 请求体要在这里携带
// 请求体支持三种内容类型, 用哪个还是要和后端对上
xhr.send(JSON.stringify({
bookname: '黑马',
author: '老李',
publisher: '黑马出版社'
}))
xhr.addEventListener('load', function () {
let result = JSON.parse(xhr.response)
console.log(result);
})
请求体内容类型有:
1.键值对字符串
2.JSON字符串
3.表单数据FormData
四.同步异步
1.同步代码: 执行顺序和书写顺序一致
2.异步代码: 不是立即执行的,执行顺序和书写顺序不一致。
js中典型的异步代码有:
1.定时器:setTimeout,setInterval
2.Ajax
3.dom事件
3.同步与异步检测题(自测)
<script src="https://cdn.jsdelivr.net/npm/axios@0.27.2/dist/axios.min.js"></script>
<script>
console.log(1)
if (true) {
console.log(2);
} else {
console.log(3);
}
setTimeout(() => {
console.log(4);
}, 1000)
const fn = () => {
console.log(5);
}
for (let i = 0; i < 3; i++) {
console.log(6);
}
console.log(7);
fn()
document.addEventListener('click', () => {
console.log(8);
})
axios({
url: 'http://ajax-api.itheima.net/api/province',
method: 'GET'
}).then(() => {
console.log(9);
})
console.log(10);
</script>
打印的结果如下,你做对了吗?
1 2 666 7 5 10 9 4
五.回调函数
是自己定义的函数,但不是自己调用的
典型特点:把一个函数当成实参传递, 将来特定的时机调用, 这个函数体就叫回调函数 。
function theFn(fn) { // fn = () => { console.log('回调函数执行') }
fn() // 代码执行到这句话, 会回调theFn()里函数体执行, 这个过程就叫回调函数
}
theFn(() => { // 此箭头函数体作为实参值传递给了thenFn的形参变量fn上
console.log('回调函数执行');
})
// 小结: 所以以后在调用一个函数时, 把函数体作为实参值传入到另一个函数内, 它应该就是一个回调函数应用了
// 例如forEach等数组方法都是回调函数的运行, 还有我们所有的异步任务都有回调函数的应用
六.回调地狱
1.回调地狱概念:
下面的代码出现了回调地狱
概念: 在回调函数内, 再嵌套回调函数, 一直嵌套下去形成了回调地狱
// 目标: 获取所有省市区数据, 随便获取
// 1. 获取所有省份列表
axios.get('http://ajax-api.itheima.net/api/province').then(res => {
// 2. 获取某个省, 对应的城市列表
let pname = res.data.data[5];
axios.get(`http://ajax-api.itheima.net/api/city?pname=${pname}`).then(res => {
// 3. 获取某个市, 对应的地区列表
let cname = res.data.data[0]
axios.get(`http://ajax-api.itheima.net/api/area?pname=${pname}&cname=${cname}`).then(res => {
console.log(res);
})
})
})
七.Promise的基本语法
它是一个ES6提出一个新语法,用来优化异步代码的写法,在前端领域是我们必须要会的一个内容
promise:承诺。
举例子(说人话):生活中.它是用来表述 对将来要发生的事情的肯定。 例如 : 高中生说,老师,我会考上一所好大学的;销售员说,老板,我一定会签下大单的;程序员说,老妈,我过年一定会带个女朋友回来的。
在ES6中,它是新增的构造器(Array, Object, Promise),用来优化实现异步操作。在没有它之前,javascript中的异步处理,大多是利用回调函数来实现的。典型的几种如下:
(1)setTimeout
(2)ajax 现在有了promise,就可以对这些异步操作进行改写了。
1.Promise经典格式
let p1 = new Promise((resolve, reject) => {
// resolve和reject是Promise内提供的2个函数, 用于回调返回结果到外面
resolve(成功结果) // 触发.then()小括号里函数体执行
reject(失败结果) // 触发.catch()小括号里函数体执行
})
p1.then((成功结果变量名) => {
}).catch((失败结果变量名) => {
}).finally(()=>{
// 一定会执行的代码
})
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”,触发.then的执行
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”,触发.catch的执行
2.Promise实例代码
熟悉下Promise的相关语法
// 语法:
/*
let Promise对象变量名 = new Promise((resolve, reject) => {
// resolve和reject是Promise内提供的2个函数, 用于回调返回结果到外面
resolve(成功结果) // 触发.then()小括号里函数体执行
reject(失败结果) // 触发.catch()小括号里函数体执行
})
Promise对象变量名.then((成功结果变量名) => {
}).catch((失败结果变量名) => {
})
*/
let p = new Promise((resolve, reject) => {
// resolve和reject是Promise内提供的2个函数, 用于回调返回结果到外面
// resolve('成功了')
reject('失败了')
})
p.then(res => {
console.log(res)
}).catch(err => {
console.error(err)
})
3.Promise图示
八.Promise的三种状态和值
1.准备:new实例化后, Promise对象(pending准备状态)
2.成功:当Promise内代码执行了resolve, 会导致所在的Promise对象 变成 fulfilled成功状态
3.失败:当Promise内代码执行了reject, 会导致所在的Promise对象 变成 rejected失败状态
(pending: 准备; resolved(或者fulfilled) 成功; rejected: 失败
// 1. 创建Promise对象并返回此对象在原地, 并立刻执行函数内代码, 交给浏览器去做倒计时了(异步, 不会阻塞主线程代码往下走, 所以继续走2)
let p = new Promise((resolve, reject) => {
setTimeout(() => {
// 3. 等2秒后, resolve触发, 影响p对象状态
resolve('成功结果') // resolve => fulfilled状态 => .then
// reject('失败结果') // reject => rejected状态 => .catch
}, 2000)
})
// 2. 立刻给p对象添加.then函数并传入函数体等待被调用接收成功结果(此是.then()小括号里函数体不会执行)
p.then(res => {
console.log(res); // 2秒后返回'成功结果'
}).catch(err => {
console.error(err)
})
九.使用promise改造回调函数
let ajax = function() {
return new Promise((resolve,reject)=>{
var xhr = new XMLHttpRequest();
xhr.open('get','http://httpbin.org/ip')
xhr.onload = function() {
resolve(xhr.responseText)
}
xhr.onerror = function (){
reject('请求接口错误')
}
xhr.send()
})
}
ajax().then(res =>{
console.log(res)
}).catch(err =>{
console.log(err)
})
十.then的格式及执行逻辑
1.then作用
then方法的作用是为Promise对象添加状态改变时的回调函数。
下面从其调用格式,执行逻辑及返回值三个方面来介绍
2.then调用格式
// p 是一个promise对象
p.then(函数1[,函数2])
3.示例
var p = new Promise((resolve,reject)=>{
reject(2)//主动调用reject,并传入实参
})
// 此时,P的状态是rejected,且值promiseValue 是2.
p.then((res)=>{
// 因为p的状态是resolved,所以这句代码不会执行。
console.log("then,ok",res)
},(err)=>{
// 因为p的状态是rejected,所以自动执行then的第二个参数,并且把promisevalue传进来。
console.log("then,err",err)
})
4.then的返回值
then()方法的返回值也是一个promise对象,但是要注意的是它的返回值是一个新的promise对象,与调用then方法的并不是同一个对象。
var p1 = new Promise(()=>{});
var p2 = p1.then(function f_ok(){}, function f_err(){});
// p2也是一个promise对象。
console.log(p1 === p2); // false
p2的状态及promiseValue如何确定?
p2的状态及promiseValue按如下规则来确定
如果p1的状态是pending,则p2的状态也是pending。
如果p1的状态是resolved,then()会去执行f_ok,则p2的状态由f_ok的返回值决定。
- 如果f_ok返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_ok函数的return值。
- 如果f_ok返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
- 如果f_ok这个函数内部发生了错误(或者是用户主动抛出错误),则p2的状态是rejected,且p2的promiseValue就是这个错误对象。
- 如果p1的状态是rejected,then()会去执行f_err,则p2的状态由f_err的返回值决定。
-
- 如果f_err返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_err函数的return值。
- 如果f_err返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
- 如果f_err这个函数内部发生了错误(或者是用户主动抛出错误),则p2的状态是rejected,且p2的promiseValue就是这个错误对象。
十一.catch的格式及用法
catch 是 then(null, reject)的别名
Promise.prototype.catch 是 Promise.prototype.then(null, reject)的别名,用于指定当promise对象的状态从pending变成rejected的回调函数 。
单独使用catch没有什么意义,它一般与then一块使用。如下:
new Promise((resolve,reject)=>{
}).then(function(result){
// 如果promise对象的状态是resolved的,就会到这里来,并且result会接收promiseValue的值
}).catch(function(err){
// 如果promise对象的状态是rejected的,就会到这里来,并且err会接收promiseValue的值
})
// 上面的代码如何拆分来写的话,等价于:
var p1 = new Promise((resolve,reject){
});
var p2 = p1.then(function(result){
});
var p3 = p2.catch(function(err){
})
1.catch的返回值
catch的返回值仍是一个promise对象,确定它的值的方式与then(null,(errVal)=>{ })的方式一致。
十二.使用Promise解决回调地狱
回调地狱代码
// 目标: 获取所有省市区数据, 随便获取
// 1. 获取所有省份列表
axios.get('http://ajax-api.itheima.net/api/province').then(res => {
// 2. 获取某个省, 对应的城市列表
let pname = res.data.data[5];
axios.get(`http://ajax-api.itheima.net/api/city?pname=${pname}`).then(res => {
// 3. 获取某个市, 对应的地区列表
let cname = res.data.data[0]
axios.get(`http://ajax-api.itheima.net/api/area?pname=${pname}&cname=${cname}`).then(res => {
console.log(res);
})
})
})
// 上面的代码就出现了回调地狱
// 概念: 在回调函数内, 再嵌套回调函数, 一直嵌套下去形成了回调地狱
Promise解决回调地狱之后代码:
// 目标: 使用Promise的链式调用解决问题
// 前提: axios函数在原地返回的就是一个Promise对象
let pname = ''
axios.get('http://ajax-api.itheima.net/api/province').then(res => {
// 2. 获取某个省, 对应的城市列表
pname = res.data.data[5];
return axios.get(`http://ajax-api.itheima.net/api/city?pname=${pname}`)
}).then(res => {
// 3. 获取某个市, 对应的地区列表
let cname = res.data.data[0]
return axios.get(`http://ajax-api.itheima.net/api/area?pname=${pname}&cname=${cname}`)
}).then(res => {
console.log(res);
})
十三.Promise的静态方法all、race、resolve和reject
1.Promise的静态方法all方法
// 目标: 讲解Promise的all方法
// 静态(类)方法: 直接用Promise类来调用
// 1. Promise.all()
// 作用: 合并多个Promise对象, 等待所有成功后, 返回结果
// 语法: Promise.all([promise对象1, promise对象2, ...]).then()
// 特点: 返回最终结果是个数组, 值是按顺序对应小Promise对象的成功结果
// 注意: 如果有1个Promise失败, 则整个Promise对象则失败
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功1')
}, 2000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功2')
}, 2000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功3')
}, 2000)
})
Promise.all([p1, p2, p3]).then(res => {
console.log(res);
})
2.Promise的静态方法race方法
// 目标: 讲解Promise的race方法
// 静态(类)方法: 直接用Promise类来调用
// 1. Promise.race() - 赛跑机制
// 作用: 发起并行多个Promise对象, 等待只要任何一个有结果(成功|失败), 返回结果执行then
// 语法: Promise.race([promise对象1, promise对象2, ...]).then()
// 特点: 返回第一个有结果的promise对象的结果
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功1')
}, 2000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功2')
}, 2000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功3')
}, 2000)
})
Promise.race([p1, p2, p3]).then(res => {
console.log(res);
})
3.Promise的静态方法resolve和reject方法
创建一个状态为resolved的promise对象,并指定其值。或者理解为:把值转成Promise对象,并设置状态和值。
const p = Promise.resolve(值)
创建一个状态为rejected的promise对象。
const p = Promise.reject(值)
十四.分类导航模板
1.分类导航html代码
<!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>20.案例_分类导航</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
a {
text-decoration: none;
color: #333;
}
ul {
list-style: none;
}
.container {
width: 980px;
margin: 0 auto;
}
.top {
display: flex;
width: 100%;
height: 80px;
align-items: center;
justify-content: space-around;
position: relative;
}
.top>li {
text-align: center;
}
.top>li>a {
border-bottom: 1px solid transparent;
}
.top>li:hover>a {
border-color: #27ba9b;
color: #27ba9b;
}
.top>li:hover>.sub {
opacity: 1;
transform: scale(1, 1);
}
.sub {
position: absolute;
left: 0;
top: 80px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 100%;
display: flex;
align-items: center;
height: 100px;
opacity: 0;
transform: scale(1, 0);
transform-origin: top center;
transition: all 0.3s .1s;
}
.sub li {
width: 120px;
text-align: center;
}
.sub li a {
display: block;
font-size: 14px;
padding-top: 8px;
}
.sub li a span {
display: block;
}
.sub li a img {
width: 60px;
height: 60px;
vertical-align: middle;
}
</style>
</head>
<body>
<div class="container">
<ul class="top">
<!-- <li>
<a href="javascript:;">首页</a>
<ul class="sub">
<li>
<a href="javascript:;">
<span>砂锅厨具</span>
<img src="https://yanxuan.nosdn.127.net/3102b963e7a3c74b9d2ae90e4380da65.png?quality=95&imageView" alt="">
</a>
</li>
</ul>
</li> -->
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios@0.27.2/dist/axios.min.js"></script>
<script>
</script>
</body>
</html>
2.分类导航Javascript代码
分析思路和数据,进行页面渲染和铺设
axios.defaults.baseURL = 'http://ajax-api.itheima.net'
// 1. 先准备一套标签和样式 (md复制-读一读对应上)
// 获取铺设前, 在读标签发现不简单
// 正常思想1: 鼠标移入事件, 再获取二级数据并铺设
// 现在思想2: 它是移入直接css方式让二级数据div直接显示(意思就是二级所有的div要提前铺设好)
// 思想1, 前面我们练习过, 不难, 思想2, 我们着重讲解下
// 2. 先获取一级分类数据列表
axios({
url: '/api/category/top'
}).then(({ data: res }) => {
// axios()在原地返回Promise对象(此Promise对象内包裹一个原生Ajax请求)
// axios它自己封装了一个结果对象, 并把后端返回的数据挂载到data属性下
// 所以: 我想让res的值直接是后端返回的数据
console.log(res); // { data: [{一级分类对象}, {一级分类对象}] }
let secondPromiseArr = res.data.map(obj => {
// 3. 遍历每个一级分类对象, 再请求下属的二级分类列表
let p = axios({
url: '/api/category/sub',
params: {
id: obj.id // 一级id
}
})
// 问题: 循环很快, 二级分类的多个请求并行, 谁先回来不一定
// 解决: 想要按顺序来, 使用Promise.all, 先返回并形成一个Promise数组
return p
})
// secondPromiseArr结果: [ 小Promise对象(负责请求二级分类), 小Promise对象 ]
// 5. 把二级分类请求Promise对象, 合并成一个大的Promise对象
Promise.all(secondPromiseArr).then(res => {
console.log(res);
// 6. 有了最终的数据, 循环产生一级和二级的标签结构并铺设到页面上
// res: [ 二级分类列表axios结果对象, 二级分类列表axios结果对象 ] (9个)
let allLiStr = res.map(result => { // result: 是二级分类列表axios结果对象
let obj = result.data.data // 二级分类数据对象
return `<li>
<a href="javascript:;">${obj.name}</a>
<ul class="sub">
${obj.children.map(item => {
return `<li>
<a href="javascript:;">
<span>${item.name}</span>
<img src="${item.picture}" alt="">
</a>
</li>`
}).join('')}
</ul>
</li>`
}).join('')
document.querySelector('.top').innerHTML = allLiStr
})
})