Ajax(下)
●http 传输协议
○http(s) 协议规定了, 只能由前端主动发起
○并且在传输的过程中, 只能传递 字符串
●http 协议过程
1.建立连接
浏览器和服务器进行连接建立
基于 TCP/IP 协议的三次握手
2.发送请求
要求前端必须以 请求报文 的形式发送
报文由浏览器组装, 我们只需要提供对应的信息即可
报文包含的内容
请求报文行
请求方式, 请求地址, 传输协议
请求报文头(对本次请求的一些说明信息)
userAgent: 请求方终端信息
accept: 期望后端返回的数据类型
content-type: 请求携带的 "材料" 的数据格式
cookie: 只要 cookie 空间内有内容, 会自动携带
请求报文空行
请求报文体(不是所有请求都有)
3.接收响应
要求后端必须以响应报文的形式返回
报文由服务器组装
响应报文包含的内容
响应报文行
响应状态码, 简单信息描述响应状态码, 传输协议
响应报文头(对本次响应的一些说明信息)
server: 哪一个服务器给你返回的信息
date: 时间, 服务器时间
content-length: 响应体长度
content-type: 响应数据类型
响应报文体(后端返回给前端的一些信息)
4.断开连接
浏览器和服务器断开连接
基于 TCP/IP 协议的四次挥手
●响应状态码
○100199 表示连接继续
○200299 表示各种成功
○300399 表示重定向
○400499 表示各种客户端错误
○500~599 表示各种服务端错误
●回调函数
○把函数 A 以实参的形式传递到 函数 B 内
○在函数 B 内以形参的方式调用到 函数 A
○此时我们可以把函数 A 叫做函数 B 的 回调函数
○我们在封装异步代码的时候会用到回调函数
function fnA () {console.log('我是 fnA 函数内部的代码')}
function fnB(callback) {callback()}
fnB(fnA)
●使用回调函数封装一个异步代码
function fn(jinnang = () => {}) {
console.log("班长去买水了");
const timer = Math.ceil(Math.random() * 3000);
setTimeout(() => {
console.log("班长买完水了");
console.log("耗时", timer);
console.log("按照锦囊内的内容行事");
jinnang();
}, timer);
}
/**
* fn 函数一旦调用, 班长出发开始去买水
* 在班长出发的时候, 给他一个锦囊
*/
fn(() => {
console.log("去买一瓶牛奶");
});
fn();
●上述代码已经完成了一个异步的封装
●不过在工作中我们更多的是封装网络请求这种异步代码
●但是我们这里通过一个 '买水耗时' 来模拟一个网络请求的延迟, 我们约定如果时间超过 3500 毫秒, 那么就算是失败, 否则就是成功
function fn(jinnang = () => {}) {
console.log("班长去买水了");
const timer = Math.ceil(Math.random() * 3000) + 2000;
setTimeout(() => {
if (timer > 3500) {
console.log("请求失败", timer);
} else {
console.log("请求成功", timer);
}
}, timer);
}
/**
* fn 函数一旦调用, 班长出发开始去买水
* 在班长出发的时候, 给他一个锦囊
*/
fn(() => {
console.log("去买一瓶牛奶");
});
fn();
●此时我们已经封装完毕了, 只不过这种封装方式会带来另一个问题, 就是回调地狱
●回调地狱: 当你使用回调函数过多的时候, 会出现的一种代码书写结构
●需求:
○再买水成功后, 让班长帮忙退掉
○在推掉以后, 再次让班长帮忙买水 (此时必须要在前一瓶水购买完成之后再去购买)
○在第二次买水成功以后, 再次让班长去买水
fn(
() => {
console.log("班长第一次买水成功, 帮我退掉");
fn(
() => {
console.log("班长第二次买水成功");
fn(
() => {
console.log("班长第三次买水成功");
},
() => {
console.log("班长第三次买水失败");
}
);
},
() => {
console.log("班长第二次买水失败");
}
);
},
() => {
console.log("班长第一次买水失败");
}
);
●这段代码运行没有任何问题, 但是阅读起来极其不利于理解
○原因:
■按照回调函数的语法进行封装, 只能通过传递一个函数作为参数来调用
■当你使用回调函数过多的时候, 会出现回调地狱的代码结构
○解决:
■不按照回调函数的语法封装
■ES6 推出了一种新的封装异步代码的方式, 叫做 Promise (承诺, 期约)
Promise
是一种异步代码的封装方案
因为换了一种封装方案, 不需要安装回调函数的方式去调用, 需要按照 promise 的形式去调用
注意 promise 不是解决 异步问题的, 而是解决回调地狱问题的
●认识 Promise
○promise 的三种状态
■持续: pending
■成功: fulfilled
■失败: rejected
○promise 的两种转换
■从持续转为成功
■从持续转为失败
○promise 的基础语法
■ES6 内置构造函数
○promise 的语法
■const p = new Promise(function () {})
○promise 对象可以触发的两个方法
■p.then(函数); 成功时执行
■p.catch(函数); 失败时执行
●promise 封装一个异步函数
const p = new Promise(function (resolve, reject) {
// resolve: 是一个形参, 名字自定义, 值是一个函数, 当你调用的时候, 会把当前 promise 的状态转换为 成功
// reject: 是一个形参, 名字自定义, 值是一个函数, 当你调用的时候, 会把当前 promise 的状态转换为 失败
// resolve 和 reject 调用时可以传递一个参数, 这个参数会被传递给对应的 then catch
const timer = Math.ceil(Math.random() * 3000) + 2000;
setTimeout(() => {
if (timer > 3500) {
console.log("买水失败, 耗时 ", timer);
reject("奖励一个bug");
} else {
console.log("买水成功, 耗时: ", timer);
resolve("送你十个bug");
}
}, timer);
});
p.then(function (address) {
console.log("班长买水成功咯~~~", address);
});
p.catch(function (address) {
console.log("班长买水失败咯~~~", address);
});
● 封装 promise 为函数
function fn() {
const p = new Promise(function (resolve, reject) {
const timer = Math.ceil(Math.random() * 3000) + 2000;
setTimeout(() => {
if (timer > 3500) {
reject("班长买水失败");
} else {