Ajax_原生学习
目标
了解原生JS如何编写和发送Ajax请求
讲解
Ajax是一个技术的名称, 而以前我们具体使用的是axios.js插件来发送请求 ,但是其实axios.js也是一个库, 内部是原生JS代码, 实际它是对原生的代码进行了封装。
今天我们就来解开神秘的面纱
Ajax如果用原生JS实现, 需要使用window提供的一个构造函数, 叫XMLHttpRequest
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原理底层依靠一个构造函数
XMLHttpRequest
Ajax_原生query传参
目标
了解Ajax原生传参使用
讲解
新建Ajax_原生传参.html
// 目标: 请求辽宁省下所有城市列表
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_原生POST
目标
了解原生POST请求和请求体使用
格式
// 1. 创建一个xhr对象
let xhr = new XMLHttpRequest()
// 2. 设置请求方式和请求地址
xhr.open(请求方式, 请求地址)
// 3. 发送请求
// 请求体
xhr.send(请求体)
// 4. 监听load(请求成功)事件 ==> 可以获取响应数据
xhr.addEventListener('load', function() {
console.log(xhr.response) // 服务器返回的数据
})
小结
- 请求体内容类型有哪三种?
键值对字符串
JSON字符串
表单数据FormData
概念_回调函数
目标
掌握回调函数的使用和执行过程,理解它和异步的关系
现象
发现前面异步的代码, 都有一个函数体, 这个函数体里代码都是等待这个异步任务有结果后执行
回调函数
是自己定义的函数,但不是自己调用的
典型特点:把一个函数当成实参传递, 将来特定的时机调用, 这个函数体就叫回调函数 。
function theFn(fn) { // fn = () => { console.log('回调函数执行') }
fn() // 代码执行到这句话, 会回调theFn()里函数体执行, 这个过程就叫回调函数
}
theFn(() => { // 此箭头函数体作为实参值传递给了thenFn的形参变量fn上
console.log('回调函数执行');
})
// 小结: 所以以后在调用一个函数时, 把函数体作为实参值传入到另一个函数内, 它应该就是一个回调函数应用了
// 例如forEach等数组方法都是回调函数的运行, 还有我们所有的异步任务都有回调函数的应用
我们之前接触的回调函数
-
arr. forEach(() =>{}), map, filter..... -
addEventlistener('click', () => {} )
小结
- 什么是回调函数?
-
把一个函数当做实参传递, 将来特定的时机调用, 这个函数体就叫回调函数
概念_回调地狱
特点:
-
发多次ajax请求
-
后一次请求要依赖上一次请求的结果
这里我们以一个, 请求省市区列表, 最终获取地区列表为例, 来体验下回调地狱, 直接复制代码查看
// 目标: 获取所有省市区数据, 随便获取
// 1. 获取所有省份列表
axios.get('url地址').then(res => {
// 2. 获取某个省, 对应的城市列表
let pname = res.data.data[5];
axios.get(`url地址`).then(res => {
// 3. 获取某个市, 对应的地区列表
let cname = res.data.data[0]
axios.get(url地址`).then(res => {
console.log(res);
})
})
})
上面的代码就出现了回调地狱
概念: 在回调函数内, 再嵌套回调函数, 一直嵌套下去形成了回调地狱
小结
- 什么是回调地狱?
在回调函数内, 再嵌套回调函数, 一直嵌套下去形成了回调地狱
Promise_基本语法
目标
掌握Promise相关语法使用
Promise
它是一个ES6提出一个新语法,用来优化异步代码的写法。
- 在前端领域是我们必须要会的一个内容
- 后续的课程中也会多次使用
promise:承诺。
- 生活中.它是用来表述 对将来要发生的事情的肯定。 例如 : 高中生说,老师,我会考上一所好大学的;销售员说,老板,我一定会签下大单的;程序员说,老妈,我过年一定会带个女朋友回来的。
- 在ES6中,它是新增的构造器(Array, Object, Promise),用来优化实现异步操作。在没有它之前,javascript中的异步处理,大多是利用回调函数来实现的。典型的几种如下:(1)setTimeout (2)ajax 现在有了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的执行
示例代码
新建html文件, 来熟悉下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)
})
图示
小结
- new 一个 Promise的格式是?
const p = new Promise((resolve, reject)=>{})
p.then().catch().finally()
- 对象内的resolve和reject函数作用?
回调外部的then/catch, 传递成功/失败的动作和结果
Promise_三种状态和值
目标
掌握Promise的三种状态和值
三种状态
(pending: 准备; resolved(或者fulfilled) 成功; rejected: 失败
准备:new实例化后, Promise对象(pending准备状态)
成功:当Promise内代码执行了resolve, 会导致所在的Promise对象 变成 fulfilled成功状态
失败:当Promise内代码执行了reject, 会导致所在的Promise对象 变成 rejected失败状态
状态的变化不可逆
值
resolve(值),这个值会自动传递给then
reject(值),这个值会自动传递给catch
示例代码
// 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对象有几种状态?
pending, fulfilled, rejected
- Promise对象的状态如何切换?
刚创建会立刻执行new Promise函数内代码, 此时为pending准备状态
当Promise对象内异步代码有结果触发resolve, 会导致Promise对象状态为fulfilled兑现成功状态
当Promise对象内异步代码有结果触发reject, 会导致Promise对象状态为rejected失败状态, 拒绝兑现承诺
使用promise改造回调函数
下面我们回到具体的问题:如何对现有的,通过回调函数来实现异步操作的代码进行改写。
基本套路
第一步:建立模板函数。这里的模板函数是指固定的套路:写一个空函数,在函数体中new 一个promise对象,并返回。
第二步:把异步功能写入构造函数中,根据实际来决定是否调用resolve,reject。
第三步:调用函数。通过fnName().then().catch() 结构来调用这个函数。
function fnName(){
const p = new Promise((resolve,reject)=>{})
return p;
}
then的格式及执行逻辑
目标
掌握then方法的使用;
作用
then方法的作用是为Promise对象添加状态改变时的回调函数。
下面从其调用格式,执行逻辑及返回值三个方面来介绍
then的调用格式
它有两个参数,每个参数都是函数。 第二个参数是可选的。
如下:
// p 是一个promise对象
p.then(函数1[,函数2])
它的两个参数都是函数。
- 第一个参数是resolved状态的回调函数。当p的状态从pending变成了resolved,函数1会执行。
- 第二个参数是rejected状态的回调函数。 当p的状态从pending变成了rejected,函数2会执行。
其中第二个参数是可选的,如果只写一个参数的话就是如下:
promise对象.then(函数1)
执行逻辑
以如下代码为例
var p = new Promise((resolve,reject)=>{
// resolve(val1);
reject(val2)
})
p.then(
okVal =>{
console.info("成功");
console.log(okValue);
},
errVal=>{
console.info("失败");
console.log(errValue);
}
)
它的两个参数都是函数,其执行逻辑是:
- 如果promise对象的状态是resolved,则then()会执行第一个函数,并传入当前的PromiseValue(即上面的val1);
- 如果promise对象的状态是rejected,则then()会执行第二个函数,并传入当前的PromiseValue(即上面的val2);
- 特别地,如果promise对象的状态是rejected,且此时then方法并没有设置第二个参数,就会向外抛出一个错误,错误的提示大约是Uncaught (in promise)。
如下是图示
示例代码1
var p = new Promise((resolve,reject)=>{
resolve(1)//主动调用resolve,并传入
})
// 此时,P的状态是resolved,且值promiseValue 是1.
p.then((res)=>{
// 因为p的状态是resolved,所以自动执行then的第一个参数,并且把promisevalue传进来。
console.log("then,ok",res)
})
执行输出结果是:then,ok,1
示例代码2
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)
})
执行输出结果是:then,err,2
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
如上代码可以说明p1.then()的结果是一个与p1不同的promise对象。换句话说,then()会封装一个全新的promise对象p2。那既然 p2也是一个promise对象,那么,p2的状态(promiseStatus)和值(promiseValue)分别是什么?
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就是这个错误对象。
特殊地,如果f_ok()中并没有return语句,则相当于是 return undefined。
catch的格式及用法
catch 是 then(null, reject)的别名
catch()的格式及用法
Promise.prototype.catch 是 Promise.prototype.then(null, reject)的别名,用于指定当promise对象的状态从pending变成rejected的回调函数 。
var p1 = new Promise((resolve,reject)=>{
reject('s')
});
p1.catch(function(err){
console.log(err);
})
// 与下面的代码等价
p1.then(null, function(err){
console.log(err);
})
单独使用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){
})
catch的返回值
catch的返回值仍是一个promise对象,确定它的值的方式与then(null,(errVal)=>{ })的方式一致。
Promise_链式调用
目标
掌握Promise的链式调用
p.then()的返回值是一个新的promsie对象
编写代码进行讲解
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功1')
}, 2000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功2')
}, 2000)
})
p.then(res => {
console.log(res);
return p2 // 如果return一个Promise对象,它的结果, 会被下个then接收
}).then(res => {
console.log(res);
})
小结
- then里return的Promise对象结果, 交到哪里执行?
- 下一个then接收成功结果
Promise_解决回调地狱
目标
使用Promise来解决回调地狱
基础代码
// 目标: 获取所有省市区数据, 随便获取
// 1. 获取所有省份列表
axios.get('url地址').then(res => {
// 2. 获取某个省, 对应的城市列表
let pname = res.data.data[5];
axios.get(`url地址`).then(res => {
// 3. 获取某个市, 对应的地区列表
let cname = res.data.data[0]
axios.get(`url地址`).then(res => {
console.log(res);
})
})
})
// 上面的代码就出现了回调地狱
// 概念: 在回调函数内, 再嵌套回调函数, 一直嵌套下去形成了回调地狱
小结
- Promise是如何解决回调地狱的问题的?
根据.then的链式调用把Promise对象串联起来
Promise的静态方法_all
目标
能说出Promise.all方法的作用
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);
})
小结
- Promise.all方法有什么作用?
-
合并并发多个Promise对象, 等待所有人成功, 按顺序接收结果
Promise的静态方法_race
目标
能说出Promise.race方法的作用
讲解
- 讲解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);
})
Promise.race([p1, p2, p3]).then(res => {
console.log(res);
})
小结
- Promise.race方法有什么作用?
合并并发多个Promise对象, 接收最先成功的结果
Promise的静态方法resolve和reject
目标
能说出Promise的静态方法resolve和reject方法作用
Promise.resolve
作用
创建一个状态为resolved的promise对象,并指定其值。或者理解为:把值转成Promise对象,并设置状态和值。
格式
const p = Promise.resolve(值)
Promise.reject
作用
创建一个状态为rejected的promise对象。
格式
const p = Promise.reject(值)
小结
- Promise.resolve和Promise.reject有什么作用?
创建一个状态为resolved, rejected的promise对象,并指定其值