Ajax_原生学习,同步异步,回调函数,回调地狱,Promise_语法学习,Promise_三种状态,Promise的静态方法_all,race,resolve和reject,

Ajax_原生学习

目标

了解原生JS如何编写和发送Ajax请求

讲解

Ajax是一个技术的名称, 而以前我们具体使用的是axios.js插件来发送请求 ,但是axios.js也是一个库, 内部是原生JS代码, 实际它是对原生的代码进行了封装。 今天我们就来解开神秘的面纱 Ajax如果用原生JS实现, 需要使用window提供的一个构造函数, 叫XMLHttpRequest

XMLHttpRequest

使用语法如下:

// 请求省份名字列表
// 接口地址: 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原理是?

  • window提供的XMLHttpRequest构造函数

Ajax_原生传参

目标

了解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);
})

小结

  1. 原生Ajax如何传递查询参数?

  • 在url?后面拼接查询参数

Ajax_原生POST

目标

了解原生POST请求和请求体使用

讲解

新建Ajax_原生POST.html, 请求POST测试接口

// 目标: 测试POST请求
// 接口地址: http://ajax-api.itheima.net/api/books
// 请求方式: POST
// 参数随意
​
let xhr = new XMLHttpRequest()
​
xhr.open('POST', `http://ajax-api.itheima.net/api/books`)
​
// 1. 请求体要在这里携带
// 请求体支持三种内容类型, 用哪个还是要和后端对上
xhr.send(JSON.stringify({
    bookname: '黑马',
    author: '老李',
    publisher: '黑马出版社'
}))
​
xhr.addEventListener('load', function () {
    let result = JSON.parse(xhr.response)
    console.log(result);
})
  1. 发现后端并没有解析到我们的参数, 通过后台提示和Network检查, 我们请求头Content-Type的值不对

  2. 解决: 所以axios会自动携带Content-Type内容请求头, 而原生的就需要自己携带啦!

​
// 2. 需要携带请求头Content-Type和固定值指定传递的请求体内容类型, 让后端用对应方式解析
// 要注意: 在open后send前之间设置
xhr.setRequestHeader('Content-Type', 'application/json')
  1. 最终落地完整代码

// 目标: 测试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. 请求体内容类型有哪三种?

  • 键值对字符串

  • JSON字符串

  • 表单数据FormData

概念_同步异步

目标

  • 掌握哪些是同步代码

  • 掌握哪些是异步代码

讲解

请先阅读如下代码的执行顺序并说出打印结果

<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. 打印的结果是

1 2 666 7 5 10 9 4
  1. 异步代码有如下, 其他都是同步按上到下逐行执行

    • setTimeout: 一次性定时器

    • setInterval: 定时器

    • Ajax

    • 事件

小结

  1. JS中都有哪些异步代码?

  • 定时器, Ajax, 事件

概念_回调函数

目标

掌握回调函数的使用和执行过程

讲解

  1. 异步一般都需要耗时, 而且不会阻塞主线程代码往下执行

  2. 那万一异步代码有了结果, 怎么能让某段代码执行呢?

JS中, 只有用函数调用的方式, 才能让某一段代码去执行

  1. 所以你发现前面异步的代码, 都有一个函数体, 这个函数体里代码都是等待这个异步任务有结果后执行

所以这个函数体, 会被回调执行

  1. 把一个函数当成实参传递, 将来特定的时机调用, 这个函数体就叫回调函数

  2. 我们可以自己定义一个回调函数, 看看代码执行顺序, 体验下回调的感觉

function theFn(fn) { // fn = () => { console.log('回调函数执行') }
    fn() // 代码执行到这句话, 会回调theFn()里函数体执行, 这个过程就叫回调函数
}
​
theFn(() => { // 此箭头函数体作为实参值传递给了thenFn的形参变量fn上
    console.log('回调函数执行');
})
​
​
// 小结: 所以以后在调用一个函数时, 把函数体作为实参值传入到另一个函数内, 它应该就是一个回调函数应用了
// 例如forEach等数组方法都是回调函数的运行, 还有我们所有的异步任务都有回调函数的应用
  1. 所以回调函数应用非常广泛, 不只是接收异步的结果, 可以恰当的流程中进行调用, 其他场景, 例如数组的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);
        })
    })
})
​
// 上面的代码就出现了回调地狱
// 概念: 在回调函数内, 再嵌套回调函数, 一直嵌套下去形成了回调地狱

小结

  1. 什么是回调地狱?

  • 在回调函数内, 再嵌套回调函数, 一直嵌套下去形成了回调地狱

Promise_语法学习

目标

掌握Promise相关语法使用

讲解

Promise在设计之初, 就是为了解决回调地狱

新建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)
})

小结

  1. Promise对象内的resolve和reject函数作用?

  • 回调外部的then/catch, 传递成功/失败的动作和结果

Promise_配合异步

目标

掌握用Promise来管理异步代码

讲解

新建Promise_配合异步.html来使用Promise包裹一个异步任务代码, 来看看效果

// 例1 配合定时器使用
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功结果')
    }, 2000)
})
​
p.then(res => {
    console.log(res); // 2秒后返回'成功结果'
})

小结

  1. Promise是否必须和异步代码使用?

  • 不一定, 看情况

Promise_三种状态

目标

了解Promise的三种状态

讲解

新建Promise_三种状态.html来讲解, 在上个代码的例子来说明为何代码会这样执行

// 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对象有状态凝固特性, 就是Promise对象确定是成功/失败, 就不能再备更改了
  1. Promise有三种状态

    • new实例化后, Promise对象(pending准备状态) -> 也是在原地给个承诺

    • 当Promise内代码执行了resolve, 会导致所在的Promise对象(fulfilled成功状态) ->  兑现状态 -> 把值传给then

    • 当Promise内代码执行了reject, 会导致所在的Promise对象(rejected失败状态) -> 拒绝状态 -> 把失败值传给catch

小结

  1. Promise对象内部有几种状态?

  • pending, fulfilled, rejected

  1. Promise对象的状态如何切换?

  • 刚创建会立刻执行new Promise函数内代码, 此时为pending准备状态

  • 当Promise对象内异步代码有结果触发resolve, 会导致Promise对象状态为fulfilled兑现成功状态

  • 当Promise对象内异步代码有结果触发reject, 会导致Promise对象状态为rejected失败状态, 拒绝兑现承诺

  1. 什么是Promise的状态凝固?

  • 当Promise对象状态改变后, 无法再次改变

Promise_链式调用

目标

掌握Promise的链式调用

讲解

编写代码进行讲解

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

小结

  1. then里return的Promise对象结果, 交到哪里执行?

  • 下一个then接收成功结果

Promise_解决回调地狱

目标

使用Promise来解决回调地狱

讲解

  1. 新建html文件, 用axios方法原地返回Promise对象特性

  2. 用.then()里return Promise对象结果会被链式调用下一个then接收特性

把回调函数嵌套的写法, 变成链式调用then的形式

// 目标: 使用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);
})

小结

  1. 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);
})

小结

  1. Promise.all方法有什么作用?

  • 合并并发多个Promise对象, 等待所有人成功, 按顺序接收结果

Promise的静态方法_race

目标

  • 能说出Promise.race方法的作用

讲解

  1. 讲解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);
})

小结

  1. Promise.race方法有什么作用?

  • 合并并发多个Promise对象, 接收最先成功的结果

Promise的静态方法resolve和reject

目标

能说出Promise的静态方法resolve和reject方法作用

讲解

了解Promise的resolve和reject方法即可

// 目标: 讲解Promise静态方法的resolve和reject
// 1. 先看如下效果, 封装求2个数的和函数
function sumFn(numA, numB) {
    if (numA && numB) {
        return numA + numB
    } else {
        return '请传递参数'
    }
}
​
let result1 = sumFn(10, 20)
let result2 = sumFn()
// 返回的结果要么是和要么是字符串, 不太方便判断是否成功/失败
​
​
// 2. 所以这个时候, 我们可以用Promise的静态方法, 把结果包装成Promise对象
​
// 3. Promise.resolve()
// 作用: 把现有的值包装成成功状态的Promise对象并返回
// 返回值: 一个成功状态的Promise对象和结果
function myFn(numA, numB) {
    if (numA && numB) {
        return Promise.resolve(numA + numB) // 现在返回的Promise对象
    } else {
        return '报错'
    }
}
​
myFn(10, 20).then(res => {
    console.log(res);
})
​
// 但是myFn().then() 这样写会报错, 因为返回的字符串无法调用then
​
// 解决, 也得把错误提示字符串包装成Promise对象
// 4. Promise.reject()
// 作用: 把现有的值包装成失败状态的Promise对象并返回
// 返回值: 一个失败状态的Promise对象和结果
function theFn(numA, numB) {
    if (numA && numB) {
        return Promise.resolve(numA + numB) // 现在返回的Promise对象
    } else {
        return Promise.reject('报错')
    }
}
​
theFn().then(res => {
    console.log(res);
}).catch(err => {
    console.error(err);
})
​
// 一般在封装库的时候常用, 所以了解其作用即可

小结

Promise.resolve和Promise.reject有什么作用?

  • 把值转成Promise对象,并设置状态和返回结果

案例封装Ajax函数查询参数

目标

了解封装原生Ajax请求的代码

讲解

新建html文件, 封装itheimaAxios函数, 并复习axios的GET和params传参使用, 暂时不考虑请求体传参

function itheimaAxios({ url: url, method: method = 'GET', params: params }) {
    // 1. 外面需要Promise对象, 所以创建一个并返回
    return new Promise((resolve, reject) => {
        // 2. 解构配置对象参数使用
        let xhr = new XMLHttpRequest()
​
        // 3. 做判断, 如果params有值, 则需要携带在url?后面
        if (params) {
            // 把对象转成key=value&key=value格式字符串
            let arr = []
            for (let key in params) {
                // 先转成数组[key=value, key=value]这样格式
                arr.push(`${key}=${params[key]}`)
            }
            url = url + '?' + arr.join('&')
​
            // 高端写法
            // url += '?' + Object.keys(params).map(key => `${key}=${params[key]}`).join('&')
        }
​
        xhr.open(method, url)
        xhr.send()
        xhr.addEventListener('load', function () {
            let result = JSON.parse(xhr.response)
            console.log(result);
        })
    })
}
​
​
// 目标: 封装Ajax函数
// 我们知道axios就是封装了原生的Ajax的代码, 我们也模拟下, 先回顾下axios方法使用
// 然后封装完上面的函数后, 把axios名字换我们自己的发现也ok, 封装成功
itheimaAxios({
    url: 'http://ajax-api.itheima.net/api/area',
    // method: 'GET',
    params: {
        pname: '辽宁省',
        cname: '大连市'
    }
}).then(res => {
    console.log(res)
}).catch(err => {
    console.error(err)
})

小结

  1. 为何使用axios的配置项key名固定的?

  • 因为函数内部会使用这些属性名

  1. axios内是如何处理params参数的?

  • 把对象转成key=value&key=value字符串格式拼接在url?后面带给服务器

案例封装Ajax函数请求体

目标

能了解Ajax函数内对请求体的处理

讲解

在上个代码基础上继续完成

function itheimaAxios({ url: url, method: method = 'GET', params: params, data: data }) {
    // 1. 外面需要Promise对象, 所以创建一个并返回
    return new Promise((resolve, reject) => {
        // 2. 解构配置对象参数使用
        let xhr = new XMLHttpRequest()
​
        // 3. 做判断, 如果params有值, 则需要携带在url?后面
        if (params) {
            // 把对象转成key=value&key=value格式字符串
            let arr = []
            for (let key in params) {
                // 先转成数组[key=value, key=value]这样格式
                arr.push(`${key}=${params[key]}`)
            }
            url = url + '?' + arr.join('&')
​
            // 高端写法
            // url += '?' + Object.keys(params).map(key => `${key}=${params[key]}`).join('&')
        }
​
        xhr.open(method, url)
​
​
        // 4. 判断如果data有值则携带
        if (data) {
            // 5. 接着判断值的3种类型
            if (data instanceof FormData) {
                xhr.send(data)
            } else if (data instanceof Object) {
                xhr.setRequestHeader('Content-Type', 'application/json')
                xhr.send(JSON.stringify(data))
            } else if (typeof data === 'string') {
                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
                xhr.send(data)
            }
        } else {
            // 如果没有请求体正常发请求
            xhr.send()
        }
​
        // 6. 处理成功/失败返回结果
​
        // 成功
        // xhr.addEventListener('load', () => {
        //     resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null)
        // })
​
        // 失败
        // xhr.addEventListener('error', () => {
        //     reject(new Error('请检查网络/各项参数的值'))
        // })
​
​
        // axios源码逻辑
        xhr.addEventListener('loadend', () => {
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null)
            } else {
                reject(new Error('请检查网络/各项参数的值'))
            }
        })
    })
}
​
// 目标: 封装Ajax函数_支持POST和请求体
// 先尝试调用, 看着需求写实现
itheimaAxios({
    url: 'http://ajax-api.itheima.net/api/books',
    method: 'POST',
    data: {
        "bookname": "黑马程序员",
        "author": "小马",
        "publisher": "北京出版社"
    }
}).then(res => {
    console.log(res)
}).catch(err => {
    console.error(err)
})

小结

  1. 我们传递的data参数请求体, 内部会如何处理?

  • 判断值的类型, 然后携带不同的请求头告诉服务器请求体内容类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值