伺服器相關概念
1. 伺服器
簡而言之就是在網上提供服務的計算機,它的本質就是網絡上的一台配置非常高的電腦。 我們是看不到的,但我們經常使用它為我們提供的服務,比如: 微信 網易雲音樂 浏覽器
2. 用戶端(浏覽器端)
簡單了解就是我們的個人電腦。個人電腦去通路伺服器提供的服務。
3. 通訊過程
以咱們用的最多的浏覽器為例,和伺服器通訊的過程就像聊微信?
- 我:---> 你好?在嗎?欠我的錢,什麼時候還?
- 他:--->不在!不還!
:point_right: 一次通訊有兩部分組成:請求 與 響應
5. url位址格式及作用
- url 格式
- 協定名
- http:
- https:
- ftp:
- 主機名
- ip位址
網絡中電腦的唯一辨別
- 域名:為了友善記憶,例如: www.baidu.com
最終也要轉換成ip位址
- 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 個狀态中每一個都有一個相關聯的非正式的名稱,下表列出了狀态、名稱和含義:
狀态 名稱 描述 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()方法。
參數-
請求所使用的HTTP方法; 例如 "GET", "POST", "PUT", "DELETE"等. 如果下個參數是非HTTP(S)的URL,則忽略該參數.method
-
該請求所要通路的URLurl
-
一個可選的布爾值參數,預設為true,意味着是否執行異步操作,如果值為false,則send()方法不會傳回任何東西,直到接受到了伺服器的傳回資料。如果為值為true,一個對開發者透明的通知會發送到相關的事件監聽者。這個值必須是true,如果multipart 屬性是true,否則将會出現一個意外。async
-
user
- 使用者名,可選參數,為授權使用;預設參數為空string.
-
密碼,可選參數,為授權使用;預設參數為空string.password
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
- 通過
方法送出符合格式要求的資料,并确認結果(可以通過JSON.stringify轉化格式)send
- 根據接口文檔要求通過
<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即可
具體參考MDN
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
- form标簽的action屬性用于設定伺服器接
示例
<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
,也可以實作類似效果,而且不僅僅是文本類資料,檔案也可以,咱們先嘗試文本類的資料
- 這裡隻介紹了常用的,詳細見文檔:MDN-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 - 基本文法
- 傳送門:MDN-Promise基本示例
- 傳送門:MDN-Promise.prototype.then
- 傳送門:MDN-Promise.prototype.catch
概念
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的錯誤.
示例代碼
<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個有結果的異步任務 應用場景:一個資料有多個接口可以擷取,等待最快的。
- Promise.all
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 對象,狀态為Promise.resolve()
resolved
- 沒有參數時,直接傳回一個
狀态的 Promise 對象resolved
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的異常