服务器相关概念
1. 服务器
简而言之就是在网上提供服务的计算机,它的本质就是网络上的一台配置非常高的电脑。 我们是看不到的,但我们经常使用它为我们提供的服务,比如: 微信 网易云音乐 浏览器
2. 客户端(浏览器端)
简单理解就是我们的个人电脑。个人电脑去访问服务器提供的服务。
3. 通讯过程
以咱们用的最多的浏览器为例,和服务器通讯的过程就像聊微信?
- 我:---> 你好?在吗?欠我的钱,什么时候还?
- 他:--->不在!不还!
:point_right: 一次通讯有两部分组成:请求 与 响应
5. url地址格式及作用
- url 格式
-
协议名
- http:
- https:
- ftp:
-
主机名
-
ip地址
网络中电脑的唯一标识
-
域名:为了方便记忆,例如: www.baidu.com
最终也要转换成ip地址
-
-
端口号
计算机安装的软件在进行网络通讯时的标识。
-
请求信息
http://www.thinker.com:8080/请求信息 端口号后面的即为请求信息,具有如下含意:
- 请求服务器上的什么文件,
- 本次请求给向服务器携带了什么样的数据,
- 本次请求有什么目的
- 等等......
-
-
AJAX 概念
认识了服务器之后,咱们来认识以下
ajax
,并且体验以下他能够实现的效果概念
AJAX 是异步的 JavaScript 和 XML(Asynchronous JavaScript And XML)。简单点说,就是使用
XMLHttpRequest
对象与服务器通信。 它可以使用 JSON,XML,HTML 和 text 文本等格式发送和接收数据。AJAX 最吸引人的就是它的“异步”特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。AJAX 应用最主要的两个特点:
-
在不重新加载页面的情况下发送请求给服务器。
-
接受并使用从服务器发来的数据。
-
不但要向服务器发起请求,还要能够接收服务器响应的结果。这一点初学者一定要意识到。
-
请求报文&响应报文
http - 请求报文
浏览器 与 服务器进行通讯时,每次浏览器发出的请求,叫请求报文
组成 与 查看方法
请求报文组成:
请求行
请求头
空行
请求体
具体格式如下图:
小结
-
请求报文是自动生成,还是人为设置的?
-
由浏览器自动生成,也可以人为的修改或添加
-
-
请求的方法和地址
-
在请求报文的哪里?
-
请求行中
-
http - 响应报文
组成 与 查看方法
浏览器 与 服务器进行通讯时,每次服务器的响应,叫响应报文
响应报文由:
状态行
响应头部
空行
响应体
具体格式如下图:
小结
-
响应报文中状态码在哪里?
状态行
-
响应报文中的服务器返回的内容在哪里?
响应(主)体
http - 响应状态码
服务器响应的内容中除了响应体以外,还有一个需要重点关注的信息,http状态码
服务器对本次请求所处理的结果以一个编码进行体现,这个状态码是我们需要关注的。
传送门:MDN-HTTP状态码 HTTP response status codes
概念 与 作用
-
概念
状态码反应了,服务器对本次请求所处理的结果,由三位数字组成。
-
作用
状态码会作为前端人员判断请求处理结果的依据。
常见的状态码
不仅仅只有这几个,这里只是列举了常见的
状态码 | 状态码描述 | 说明 |
---|---|---|
200 | OK | 请求成功。 |
201 | Created | 资源在服务器端已成功创建。 |
304 | Not Modified | 资源在客户端被缓存,响应体中不包含任何资源内容! |
400 | Bad Request | 客户端的请求方式、或请求参数有误导致的请求失败! |
401 | Unauthorized | 客户端的用户身份认证未通过,导致的此次请求失败! |
404 | Not Found | 客户端请求的资源地址错误,导致服务器无法找到资源! |
500 | Internal Server Error | 服务器内部错误,导致的本次请求失败! |
小结
状态码很多,但对于前端更多的是关注以下几个状态码:
-
200
请求被成功处理
-
401
在涉及到身份认证操作时,身份认证失败
-
404
url地址错误
-
400
请求参数错误
XMLhttpRequest介绍
Ajax(Asynchronous JavaScript and XML)不是指一种单一的技术,而是有机地利用了一系列相关的技术。虽然其名称包含XML,但实际上数据格式可以由JSON代替,进一步减少数据量,形成所谓的AJAJ。为了使用JavaScript向服务器发出 HTTP 请求,需要一个提供此功能的类的实例。这就是XMLHttpRequest的由来。这样的类最初是在Internet Explorer中作为一个名为XMLHTTP的ActiveX对象引入的。然后,Mozilla,Safari和其他浏览器,实现一个XMLHttpRequest类,支持Microsoft的原始ActiveX对象的方法和属性。同时微软也实现了XMLHttpRequest
-
-
header
将要被赋值的请求头名称
-
value
给指定的请求头赋的值
-
-
显而易见XMLHttpRequest类是重中之重了。
XMLhttpRequest属性
-
onreadystatechange
一个JavaScript函数对象,当readyState属性改变时会调用它。回调函数会在user interface线程中调用。
readyState
HTTP 请求的状态.当一个 XMLHttpRequest 初次创建时,这个属性的值从 0 开始,直到接收到完整的 HTTP 响应,这个值增加到 4。
5 个状态中每一个都有一个相关联的非正式的名称,下表列出了状态、名称和含义:
状态 名称 描述 0 Uninitialized 初始化状态。XMLHttpRequest 对象已创建或已被 abort() 方法重置。 1 Open open() 方法已调用,但是 send() 方法未调用。请求还没有被发送。 2 Sent Send() 方法已调用,HTTP 请求已发送到 Web 服务器。未接收到响应。 3 Receiving 所有响应头部都已经接收到。响应体开始接收但未完成。 4 Loaded HTTP 响应已经完全接收。 readyState 的值不会递减,除非当一个请求在处理过程中的时候调用了 abort() 或 open() 方法。每次这个属性的值增加的时候,都会触发 onreadystatechange 事件句柄。
responseText
目前为止为服务器接收到的响应体(不包括头部),或者如果还没有接收到数据的话,就是空字符串。
如果 readyState 小于 3,这个属性就是一个空字符串。当 readyState 为 3,这个属性返回目前已经接收的响应部分。如果 readyState 为 4,这个属性保存了完整的响应体。
如果响应包含了为响应体指定字符编码的头部,就使用该编码。否则,假定使用 Unicode UTF-8。
responseXML
对请求的响应,解析为 XML 并作为 Document 对象返回。
status
由服务器返回的 HTTP 状态代码,如 200 表示成功,而 404 表示 "Not Found" 错误。当 readyState 小于 3 的时候读取这一属性会导致一个异常。
statusText
这个属性用名称而不是数字指定了请求的 HTTP 的状态代码。也就是说,当状态为 200 的时候它是 "OK",当状态为 404 的时候它是 "Not Found"。和 status 属性一样,当 readyState 小于 3 的时候读取这一属性会导致一个异常。
XMLHttpRequest方法
abort()
取消当前响应,关闭连接并且结束任何未决的网络活动。
这个方法把 XMLHttpRequest 对象重置为 readyState 为 0 的状态,并且取消所有未决的网络活动。例如,如果请求用了太长时间,而且响应不再必要的时候,可以调用这个方法。
getAllResponseHeaders()
把 HTTP 响应头部作为未解析的字符串返回。
如果 readyState 小于 3,这个方法返回 null。否则,它返回服务器发送的所有 HTTP 响应的头部。头部作为单个的字符串返回,一行一个头部。每行用换行符 "\r\n" 隔开。
getResponseHeader()
返回指定的 HTTP 响应头部的值。其参数是要返回的 HTTP 响应头部的名称。可以使用任何大小写来制定这个头部名字,和响应头部的比较是不区分大小写的。
该方法的返回值是指定的 HTTP 响应头部的值,如果没有接收到这个头部或者 readyState 小于 3 则为空字符串。如果接收到多个有指定名称的头部,这个头部的值被连接起来并返回,使用逗号和空格分隔开各个头部的值。
open()
初始化一个请求. 该方法用于JavaScript代码中;如果是本地代码, 使用 openRequest()方法代替.
注意: 在一个已经激活的request下(已经调用open()或者openRequest()方法的request)再次调用这个方法相当于调用了abort()方法。
参数
-
method
请求所使用的HTTP方法; 例如 "GET", "POST", "PUT", "DELETE"等. 如果下个参数是非HTTP(S)的URL,则忽略该参数.
-
url
该请求所要访问的URL
-
async
一个可选的布尔值参数,默认为true,意味着是否执行异步操作,如果值为false,则send()方法不会返回任何东西,直到接受到了服务器的返回数据。如果为值为true,一个对开发者透明的通知会发送到相关的事件监听者。这个值必须是true,如果multipart 属性是true,否则将会出现一个意外。
-
user
- 用户名,可选参数,为授权使用;默认参数为空string.
-
password
密码,可选参数,为授权使用;默认参数为空string.
send()
发送 HTTP 请求,使用传递给 open() 方法的参数,以及传递给该方法的可选请求体。
setRequestHeader()
向一个打开但未发送的请求设置或添加一个 HTTP 请求(设置请求头)。
参数
-
Ajax原生实现
-
实例化
XMLHttpRequest
异步对象let xhr = new XMLHttpRequest() XMLHttpRequest 是内置的异步对象
-
设置 请求方式 与 请求地址
xhr.open( 请求方式, 请求地址 ) 相当于axios配置对象里的 url与method
-
发送请求
-
xhr.send()
-
注册 处理响应的回调函数
xhr.onload = function(){} 相当于then()里的回调函数
原生git传参
-
get方法如何传递参数
-
直接在url后拼接即可, url?key=value&key=value
<script> document.querySelector('button').onclick = function () { // 1. 实例化 `XMLHttpRequest` 异步对象 let xhr = new XMLHttpRequest() // 2. 设置 请求方式 与 请求地址 xhr.open('get', 'https://autumnfish.cn/api/joke?name=zs&age=20') // 3. 发送请求 xhr.send() // 4. 注册 处理响应的回调函数 xhr.onload = function () { console.log(xhr.response); } } </script>
原生post传数据
语法
-
xhr.send(数据)
-
用于向服务器发送数据
-
-
xhr.setRequestHeader('content-type', '数据的格式')
只要是数据就要明确的告诉服务器所发送的数据的格式是什么! axios会自动根据数据格式,自动设置content-type, 由于是原生语法,所以要手动设置content-type 👉 setRequestHeader()要在send()之前设置,因为只要执行了send()本次请求就完成了,后面的代码与本次的请求没有任何关系了。
<script> document.querySelector('button').onclick = function(){ let xhr = new XMLHttpRequest() xhr.open('post', 'https://autumnfish.cn/api/form/urlencoded') xhr.onload = function(){ console.log(xhr.response); } // 设置content-type xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded') // urlencoded格式 let data = 'username=thinker&age=20' xhr.send(data) } </script>
传递 json 格式
-
请求地址:https://autumnfish.cn/api/form/json
-
请求方法:
post
-
请求参数:
-
说明:
content-type
为application/json
-
提交的数据格式为
JSON
-
-
测试:
-
根据接口文档要求通过
setRequestHeader
设置content-type
请求头 -
通过
send
方法提交符合格式要求的数据,并确认结果(可以通过JSON.stringify转化格式)
-
<script> document.querySelector('button').onclick = function(){ let xhr = new XMLHttpRequest() xhr.open('post', 'https://autumnfish.cn/api/form/json') xhr.onload = function(){ // xhr.response接收响应的数据 console.log(xhr.response); // JSON.parse解析接收到的响应数据 console.log(JSON.parse(xhr.response)); } // 设置content-type xhr.setRequestHeader('content-type', 'application/json') // JSON let data = {username:'thinker',age:20}; let strJSON = JSON.stringify(data) xhr.send(strJSON) }
接收 与 解析
-
接收
-
原生ajax请求后,服务器响应的数据要通过 xhr.response来接收
-
-
解析
服务器返回的数据也有多种格式之分,现在使用最广泛的就是json,早期还用过xml。 只要是接口返回的数据几乎都是JSON格式。 解析响应数据也就是解析JSON格式的数据,使用JSON.parse(),如果是向服务器传递则使用JSON.stringify()
Axios
-
AJAX是一种技术,在不重新加载页面的情况下发送请求给服务器。但是原生的代码晦涩难懂,对于初学者很不友好。
-
有一些人对原生代码进行了封装,简化了原生代码的操作。初学者就可以使用简化后的代码完成ajax操作。
-
axios
是目前最为流行的代码库,在浏览器端是基于Ajax
封装
axios - get请求语法
语法一
// 无参数 axios.get(url) // 有参数,参数拼接在URL中 axios.get(url?key=value&key=value) -------------------------------------- // 1. 无参请求 axios.get('https://autumnfish.cn/api/joke').then(function(response){ console.log(response); }) // 2. 有参数,通过url传递 axios.get('https://autumnfish.cn/api/joke/list?num=3').then(function(response){ console.log(response);
语法二(推荐)
使用 params 发起带参请求
推荐方式 axios({ method:'GET', url:'https://autumnfish.cn/api/joke', params:{num:3} }).then(function(res){console.log(res)})
axios - post请求
语法一
axios.post(url,{key:value,key:value}).then(function(response){ console.log(response) })
语法二(推荐)
data
是作为请求体被发送的数据
-
仅适用 PUT , POST , DELETE 和 ATCH 请求方法
推荐方式 axios({ method:'POST', url:'https://autumnfish.cn/api/user/check', data:{name:zs,age:18} }).then(function(res){console.log(res)})
设置content-type 语法
-
如果是人为的使用axios向服务器发送数据,就需要设置相应的 content-type
-
前端,只会向服务器发送数据,所以只需要设置请求报文的 content-type即可
axios 设置 content-type
axios 在配置对象中通过 headers 来设置content-type,格式:
axios.post(url,data,{headers:{'content-type':'内容格式类型'}}) axios({ url:'', method:'post', data:{}, headers:{ 'content-type':'内容格式类型' } })
接口中的 content-type
不同接口对于提交数据格式的要求略有不同,咱们结合3个测试用接口,来看看如何通过axios
如何提交不同格式的数据,之后看到类似的需求能够选择对应的格式进行提交
测试接口
1.FormData数据提交 接口
请求地址:https://autumnfish.cn/api/form/formdata
请求方法:
post
请求参数:
说明:
content-type
为multipart/form-data
提交
FormData
即可
axios({ url:'https://autumnfish.cn/api/form/formdata', method:'post', data:fd, headers:{'content-type':'multipart/form-data'} })
2.application/json数据提交 接口
请求地址:https://autumnfish.cn/api/form/json
请求方法:
post
请求参数:
说明:
content-type
为application/json
提交JS对象即可
axios({ url:'https://autumnfish.cn/api/form/json', method:'post', data:{ name:'thinker', age:20, }, headers:{'content-type':'application/json'} })
3.application/x-www-form-urlencoded数据提交 接口
请求地址:https://autumnfish.cn/api/form/urlencoded
请求方法:
post
请求参数:
说明:
content-type
为application/x-www-form-urlencoded
通过data提交
key=value&key2=valu2
这种格式
axios({ url:'https://autumnfish.cn/api/form/urlencoded', method:'post', data:{ name:'thinker', age:20, }, headers:{'content-type':'application/x-www-form-urlencoded'} })
axios默认配置
全局默认请求基地址 axios.defaults.baseURL = 'https://api.example.com'; 全局默认token axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; 全局默认请求头配置 axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
拦截器
在请求或响应被 then 或 catch 处理前拦截它们。
// 添加请求拦截器 axios.interceptors.request.use(function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); // 添加响应拦截器 axios.interceptors.response.use(function (response) { // 2xx 范围内的状态码都会触发该函数。 // 对响应数据做点什么 return response; }, function (error) { // 超出 2xx 范围的状态码都会触发该函数。 // 对响应错误做点什么 return Promise.reject(error); });
演示
axios.interceptors.request.use(function(config){ console.log('请求拦截器执行了') return config },function(err){}) axios.interceptors.response.use(function(res){ console.log('响应拦截器执行了') const a = 'jh' //设置响应的数据 return a },function(err){}) axios({ method:'POST', url:'http://ajax-api.itheima.net/register', data:{ username:'ahsdfsdddjkmzzh', password:'123456' }, a:(function() { console.log('请求')})() } ).then(function(res){ console.log('响应') console.log(res) }) ------------------------------------------- //执行顺序 请求 设置拦截器.html:14 请求拦截器执行了 设置拦截器.html:19 响应拦截器执行了 设置拦截器.html:34 响应 设置拦截器.html:35 jh
如果你稍后需要移除拦截器,可以这样:
const myInterceptor = axios.interceptors.request.use(function () {/*...*/}); axios.interceptors.request.eject(myInterceptor);
可以给自定义的 axios 实例添加拦截器。
const instance = axios.create(); instance.interceptors.request.use(function () {/*...*/});
错误处理
axios.get('/user/12345') .catch(function (error) { if (error.response) { // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围 console.log(error.response.data); console.log(error.response.status); console.log(error.response.headers); } else if (error.request) { // 请求已经成功发起,但没有收到响应 // `error.request` 在浏览器中是 XMLHttpRequest 的实例, // 而在node.js中是 http.ClientRequest 的实例 console.log(error.request); } else { // 发送请求时出了点问题 console.log('Error', error.message); } console.log(error.config); });
form表单提交数据
-
真实项目中的数据都是由浏览者输入的,而form表单就是被设计用来收集浏览者输入的数据,form表单不但具有采集数据的作用,还有提交数据的能力(不需要JavaScript)。
-
但原生的提交方式会造成页面跳转,所以这种方式现在几乎不再使用,了解即可。
form原生提交
-
form - 原生提交数据的基本配置
-
form标签的action属性用于设置服务器接
用于设置服务器接口 form标签内所有的表单元素都会提交到action指向的url
-
form标签的method属性
用于设置请求的方式 get或post
-
所有的表单元素必须设置name属性
name属性用于设置服务器所接收的数据项的key
-
提交按钮 submit
-
示例
<body> action用于设置表单提交时所请求的接口 method用于设置请求方式 <form action="请求url 地址" method="请求方式"> name 属性值 将作为 key 表单输入的内容 将作为 value <input class="username" type="text" placeholder="用户名" name="username"> <input class="password" type="password" placeholder="密码" name="password"> <input class="submit" type="submit" value="提交"> </form> </body>
ajax提交数据
虽然form表单的原生提交数据已成为过去,但我们仍然需要使用form表单来收集数据,而使用ajax提交,来替换原生的提交
步骤
-
阻止默认行为
由于ajax提交,与form表单提交都需要通过点击 提交按钮。而form表单的原生提交属于表单的默认行为,所以需要阻止这个默认行为,而执行js的ajax提交行为
-
收集表单数据
获取表单数据,组织成 axios 的data 参数
-
发起ajax请求
按之前的方式发起ajax请求, 选择方法,设置url,稍许的不同在于数据是从表单中获取到的。
示例
<script> let oBtn = document.querySelector('.submit') // 为按钮 .submit 按钮注册事件 let oBtn = document.querySelector('.submit') oBtn.onclick = function(e){ // 阻止表单的默认行为 e.preventDefault() // 收集表单数据 let data = { username:document.querySelector('.username').value, password:document.querySelector('.password').value } // console.log(data); // 发起 ajax 请求 axios.get("https://autumnfish.cn/api/form/submit",{params:data}).then(function(res){ console.log(res); }) </script>
但是:这种方法需要自己 自己构造key:value数据很繁琐
form-serialize插件
如果form表单内有很多的表单项,取值的代码也会有很多,这一节咱们来学习form-serialize插件来简化取值
使用步骤
-
1.引入form-serialize
引入后会在全局注册一个serialize()方法
-
2.调用serialize即可得到表单内所有的数据
serialize(form标签对象, {hash:true})
表单数据被组织成对象 {key:value, key:value}
serialize(form标签对象, {hash:false})
表单数据被组织成 key=value&key=value 格式的键值对
注意:表单元素必须有name属性,
示例
<!-- 1. 导入 form-serialize插件 --> <script src="./02-其他资料/lib/form-serialize.js"></script> <script> let oBtn = document.querySelector('.submit') oBtn.onclick = function(e){ e.preventDefault() // 2. 调用 form-serialize方法 let oForm = document.querySelector('form') let data = serialize(oForm, { hash: true }) // console.log(data); axios.get("https://autumnfish.cn/api/form/submit", {params:data}).then(function(res){ console.log(res); }) } </script>
FormData 基本用法
上一节咱们是通过插件来获取表单数据,JavaScript提供了一个内置对象FormData
,也可以实现类似效果,而且不仅仅是文本类数据,文件也可以,咱们先尝试文本类的数据
简介
-
FormData是浏览器内置的对象,其作用等价于 form-serialize。
-
FormData对象内部使用key value形式存储表单数据项
-
能够结合ajax进行操作
基本语法
-
1.实例化FormData对象
-
new FormData(form标签对象)
由form标签创建FormData对象,在有form标签的情况下
-
new FormData()
创建一个空的FormData对象,在没有form标签的情况下使用
-
-
2.实例上的方法
-
.get(key)
返回在 FormData
对象中与给定键关联的第一个值-
.append(key, value)
向
FormData
中添加新的属性值,FormData
对应的属性值存在也不会覆盖原值,而是新增一个值,如果属性不存在则新增一项属性值-
.set(key,value)
给
FormData
设置属性值,如果FormData
对应的属性值存在则覆盖原值,否则新增一项属性值 -
示例
<script> let oBtn = document.querySelector('.submit') oBtn.onclick = function(e){ e.preventDefault() // 1. 基于表单 实例化 FormData 对象 let oForm = document.querySelector('form') let fd = new FormData(oForm) // console.log(fd); // 2. 直接将 FormData 实例作为数据传递 axios.get("https://autumnfish.cn/api/form/submit", {params:fd}).then(function(res){ console.log(res); }) } </script>
文件上传表单 - 补充
accept属性
accept属性用于过滤出指定类型的文件供选择 MDN传送门
<input accept="image/png, image/jpg" type="file" name="avatar" placeholder="请选择头像"> <input accept=".png, .jpg" type="file" name="avatar" placeholder="请选择头像">
onchange事件
选择的文件变更时触发此事件
<script> // 选择的文件变更时触发此事件 document.querySelector('input').onchange = function(){ // console.log('hello'); } </script>
获取文件对象
<script> // 选择的文件变更时触发此事件 document.querySelector('input').onchange = function(e){ // console.log('hello'); // console.log(this.value); // 获取的是选择的路径名,这一个字符串 console.log(e.target.files[0]); // 获取选择的文件对象 } </script>
小结
-
accept属性是否可限制用户选择文件
不可以,仅是简单的过滤
-
onchange事件什么时候触发
当选择的文件有所变化时
-
如何获取 文件对象
e.target.files[0]
promise
promise-介绍
Promise
,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大
-
在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码
setTimeout(function(){ console.log('第1步'); setTimeout(function(){ console.log('第2步'); setTimeout(function(){ console.log('第3步'); setTimeout(function(){ console.log('第4步'); },1000) },3000) },2000) },4000)
-
阅读上面代码,是不是很难受,上述形成了经典的回调地狱
-
现在通过
Promise
的改写上面的代码
new Promise(function (resolve, reject) { setTimeout(function () { resolve('第 1 步') }, 3000) }).then(function (res) { // 属于第1个new Promise实例 console.log(res); //返回上一个执行成功的数据 return new Promise(function (resolve, reject) { setTimeout(function () { resolve('第 2 步') }, 2000) }) }).then(function (res) { console.log(res); return new Promise(function (resolve, reject) { setTimeout(function () { resolve('第 3 步') }, 5000) }) }).then(function (res) { console.log(res) })
瞬间感受到promise
解决异步操作的优点:
-
链式操作减低了编码难度
-
代码可读性明显增强
下面我们正式来认识 promise:
Promise - 基本语法
概念
Promise是一个对象,内部执行指定异步任务,并在成功后失败时执行预定的处理代码。
基本语法
Promise相关的有三要素:所有执行异步任务,成功后的代码,失败后的代码,这三要素都要体现在语法里。
语法格式
1.Promise是一个对象
new Promise()
2.Promise实例化时必须传递一个回调函数
new Promise( function(){} )
3.回调函数必须有两从此参数
new Promise( function(resolve, reject){ })
回调函数的使用原则
1.回调函数内部主要用于执行异步操作(时间不确定的操作)
new Promise( function( resolve, reject ){ // 这里执行异步操作 })
2.根据异步操作结果的选择性的调用resolve 或 reject
new Promise( function(resolve, reject){
let 结果 = 这里执行异步操作
if(结果成功){
resolve(结果)
}else{
reject(失败) }
})
这里一定要注意,异步操作虽然在回调函数内部执行,但回调函数的结果并不在回调函数内处理。
分情况处理结果
new Promise( function(resolve, reject){
let 结果 = 这里执行异步操作
if(成功){
resolve(结果)
}else{
reject(失败)
}
}).then( function(data){
// resolve会跳到then里执行,data就是resolve()传递过来的数据
}).catch( function(err){
// reject会跳到catch里执行,err就是reject()传递过来的错误
})
说明:
-
new Promise()
创建对象,执行异步任务,根据结果选择性执行resolve(数据) 或 reject(错误) resolve()与reject()并不是处理结果,只是通过这种语法将成功情况的代码,与失败情况的代码放到Promise回调函数的外面进行处理,由此减少了一层函数的嵌套。
-
then() 成功时的处理
Promise回调函数,执行异步任务时,如果异步结果是失败的,将会使用reject,将错误 导向catch代码块 进行处理。
-
catch() 失败时的处理
Promise的catch方法,用于处理reject传递过来的错误,
-
finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
-
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
可以这样理解,通过 Prmoise 来执行异步函数,可以在回调函数外部处理异步操作的结果
示例代码
<script> let pro = new Promise(function(resolve,reject){ // setTimeout代表一个异步操作 setTimeout(function(){ // 模拟异步操作产生的数据 let v = Math.floor(Math.random()*10 + 1); // 将成功或失败的发送到其他代码块 if(v % 2 ==0){ resolve('成功的数据') }else{ reject('失败的原因') } }) }) pro.then(function(data){ // 成功时处理的代码块 console.log('成功 then被执行'); }).catch(function(err){ // 失败时处理的代码块 console.log('失败 catch被执行'); }) </script>
小结
-
1.如何理解Promise?
Promise本质就是一个对象,用于执行指定的异步任务的工具代码。
-
2.Promise语法两部分?
创建对象并指派任务 分情况处理结果
-
3.创建Promise对象时相关的参数要求?
提供回调函数,回调函数里定义两个参数resolve与reject
-
4.resolve与reject分别什么时候调用?
resolve成功时调用,传递数据 reject失败时调用,传递错误信息
-
5.resolve、reject与then、catch的关系?
resolve对应then reject对应catch
Promise - 链式调用
-
语法
new Promise().then().then().then()...
<script> new Promise(function(resolve,reject){ resolve('hello 1') }).then(function(res){ // 属于第1个new Promise实例 console.log(res); return new Promise(function(resolve,reject){ resolve('hello 2') }) }).then(function(res){ console.log(res); return new Promise(function(resolve,reject){ resolve('hello 3') }) }).then(function(res){ console.log(res); }) </script>
小结
-
then()属于哪个Promise对象
-
前面的最近的then里的return 的Promise实例对象
-
Promise的三种状态
在Promise执行的过程中,内部会经历三种状态:pendding
、fulfilled
、rejected
.传送门:MDN-Promise
-
小结:三种状态
-
pendding :初始状态,等待结果 对应的代码为new Promise()
-
fulfilled :收到结果,结果是成功 对应的代码为resolve()
-
rejected :收到结果,结果是失败 对应的代码为reject()
-
实际开发中,我们更多使用的是别人提取好的,例如: axios
promise- 构造函数方法
Promise
构造函数存在以下方法:
all()
race()
allSettled()
resolve()
reject()
try()
Promise.all
Promise除了按顺序一次一个一次一个的执行异步任务外,还可以一次执行多个异步任务。 传送门:Promise.all
-
all是静态方法 静态方法是通过函数 点 出来的方法
-
语法:
Promise.all( [ promise1, promise2 , ... ] ).then(function(data){ }).catch(function(err){ })
说明
-
1.用数组存储多个promise实例,每个实例指派了一个异步任务,再把这个数组传递给all方法,这样就开始带着多个任务执行
-
2.
Promise.all
等待 所有都成功 或 任何一个失败-
全部成功:then接收到的数据是所有的promise实例resolve的数据,是一个数组
-
只要有1个失败:catch接收到的是第1个失败的reject的错误.
注意:最算有失败的出现,每一个promise实例也要执行
-
示例代码
<script> let proa = new Promise(function (resolve, reject) { resolve('proa resolve的结果'); // reject('proba reject的错误') }) let prob = new Promise(function (resolve, reject) { resolve('prob resolve的结果'); // reject('prob reject的错误') }) let proc = new Promise(function (resolve, reject) { resolve('proc resolve的结果'); // reject('proc reject的错误') }) // 传递多个 promise实例给 Promise.all() Promise.all([proa, prob, proc]).then(function (data) { console.log(data); }).catch(function (err) { console.log(err); }) </script>
Promise.race
用于同时指派多个异步任务时使用,取第1个有结果的异步任务的结果作为Promise.race的结果。传送门:Promise.race
语法:
Promise.race([ promise1, promise2, ... ]).then(function(data){ }).catch(function(err){ })
说明
-
用数组存储多个promise实例,每个实例指派了一个异步任务,再把这个数组传递给race方法,这样就开始带着多个任务执行
-
Promise.race
取第1个有结果的异步的结果作为Promise.race的结果。也就是第1个执行了resolve或reject的promise。注意不是传递的第1个,由于Promise里的异步任务完成的时间是不同的,哪个Promise有结果,就取哪个结果作为Promise.race的结果
示例代码
let proa = new Promise(function (resolve, reject) { setTimeout(function () { resolve('proa resolve的结果'); // reject('proa reject的结果'); }, 2000) }) let prob = new Promise(function (resolve, reject) { setTimeout(function () { resolve('prob resolve的结果'); // reject('prob reject的结果'); }, 1000) }) let proc = new Promise(function (resolve, reject) { setTimeout(function () { resolve('proc resolve的结果'); // reject('proc reject的结果'); }, 3000) }) // 传递多个 promise实例给 Promise.race() Promise.race([proa, prob, proc]).then(function (data) { console.log(data); }).catch(function (err) { console.log(err); })
-
Promise.all 与 Promise.race的区别,及应用场景
-
Promise.all
所有的都成功,或要有1个失败 应用场景:页面的渲染需要多个接口的数据
-
Promise.race
等待第1个有结果的异步任务 应用场景:一个数据有多个接口可以获取,等待最快的。
-
allSettled()
Promise.allSettled()
方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例
只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束
const promise1 = Promise.resolve(3); const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo')); const promises = [promise1, promise2]; Promise.allSettled(promises). then((results) => results.forEach((result) => console.log(result.status))); // 结果 // "fulfilled" // "rejected"
resolve()
将现有对象转为 Promise
对象
Promise.resolve('foo') // 等价于 new Promise(function(resolve){ resolve('foo') })
参数可以分成四种情况,分别如下:
-
参数是一个 Promise 实例,
promise.resolve
将不做任何修改、原封不动地返回这个实例 -
参数是一个
thenable
对象,promise.resolve
会将这个对象转为Promise
对象,然后就立即执行thenable
对象的then()
方法 -
参数不是具有
then()
方法的对象,或根本就不是对象,Promise.resolve()
会返回一个新的 Promise 对象,状态为resolved
-
没有参数时,直接返回一个
resolved
状态的 Promise 对象
reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为 rejected
const p = Promise.reject('出错了'); // 等同于 const p = new Promise((resolve, reject) => reject('出错了')) p.then(null, function (err) { // null代表没有执行成功 console.log(err) }); // 出错了
Promise.reject()
方法的参数,会原封不动地变成后续方法的参数
Promise.reject('出错了') .catch(e => { console.log(e === '出错了') }) // true
使用场景
将图片的加载写成一个Promise
,一旦加载完成,Promise
的状态就发生变化
const preloadImage = function (path) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; }); };
通过链式操作,将多个渲染数据分别给个then
,让其各司其职。或当下个异步请求依赖上个请求结果的时候,我们也能够通过链式操作友好解决问题
// 各司其职 getInfo().then(res=>{ let { bannerList } = res //渲染轮播图 console.log(bannerList) return res }).then(res=>{ let { storeList } = res //渲染店铺列表 console.log(storeList) return res }).then(res=>{ let { categoryList } = res console.log(categoryList) //渲染分类列表 return res })
通过all()
实现多个请求合并在一起,汇总所有请求结果,只需设置一个loading
即可
function initLoad(){ // loading.show() //加载loading Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{ console.log(res) loading.hide() //关闭loading }).catch(err=>{ console.log(err) loading.hide()//关闭loading }) } //数据初始化 initLoad()
通过race
可以设置图片请求超时
//请求某个图片资源 function requestImg(){ var p = new Promise(function(resolve, reject){ var img = new Image(); img.onload = function(){ resolve(img); } //img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg"; 正确的 img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1"; }); return p; } //延时函数,用于给请求计时 function timeout(){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ reject('图片请求超时'); }, 5000); }); return p; } Promise .race([requestImg(), timeout()]) .then(function(results){ console.log(results); }) .catch(function(reason){ console.log(reason); });
如何保证一个promise失败之后,promise.all还能正常收到结果
const promise1 = Promise.resolve(3); const promise2 = Promise.reject(new Error()); const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([promise1, promise2, promise3].map(p=>p.catch(e=>e))).then((values) => { console.log(values); });
async函数 与 await
-
Promise已经可以很好的解决回调嵌套问题啦,咋又来一个?他的作用是简化Promise调用时的写法,把最后一层then也给拿掉,使用 = 来接收异步的结果
-
简单粗暴的理解方式就是:不再使用 .then(function(数据){}),而直接使用 = 来接收数据 传送门:async函数
async 函数基本使用
语法
async function 函数名(){ }
-
说明
-
使用async修饰的函数就是async函数
-
async函数与普通函数区在于返回值
-
async函数会隐匿返回一个Promise对象,所以async函数调用后可以直接进行 .then() 操作 而函数内部的return则会将数据 传递给 then的回调函数
-
-
原理
-
可以理解async函数内有两个return,隐式的return,显示的return
-
隐式的return返回一个Promise对象,用于调用后面的thne 而显示的return返回数据给then内的回调函数
代码示例
<script> /* // 定义一个async函数 async function fn() { } // async 会隐式返回一个Promise对象 console.log(fn()); // 所以可以调用后面的 then(function(res){}) fn().then(function (res) { console.log('then被调用了'); }) */ async function fn() { // 显示return一个数据 return 100 } fn().then(function(res){ // 显示return的数据会传递给 then的回调函数的参数res console.log(res); }) </script>
async函数与await
-
async函数 与 await,配合使用可以简化Promise的resolve数据的接收
语法格式
<script> async function fn(){ let 变量 = await Promise对象 } </script>
语法格式
<script> // async函数 与 await,配合使用可以简化Promise的resolve数据的接收 // Promise resolve的数据 默认接收方式 new Promise(function(resolve,reject){ resolve('hello') }).then(function(res){ // promise内的resolve数据需要使用 .then进行接收 console.log(res); }) // async 与 await简化接收方式 async function fn1(){ let res = await new Promise(function(resolve,reject){ resolve('hello') }) console.log(res); } fn1(); </script>
async 异常捕获
-
Promise有两种结果:resolve结果,与reject结果
-
上一小节中讲解了使用async函数简化了Promise实例的resolve结果,那么reject的结果如何处理呢?
-
解决办法使用,使用try ... catch 代替 .catch()
// 异常处理语法回顾 try{ }catch(e){ } try ... catch 可以将try块内发现的异常捕获到catch块内进行处理
代码示例
<script> // async可以使用 await 来处理Promise内部resolve的数据 // 但 Promise的reject的情况如何来处理呢? // async 与 await简化接收方式 async function fn1(){ try { let res = await new Promise(function(resolve,reject){ if(Math.floor(Math.random()*10) % 2 == 0){ resolve('成功') }else{ reject('失败') } }) console.log(res); } catch (error) { console.log(error); } } fn1(); </script>
-
小结重点:
-
async函数里的错误(promise实例reject的错误)可以使用什么捕获?
try ... catch 是通用的异常处理语法,所有的异常都可以捕获 而.catch只能捕获 Promise的异常