代碼倉庫:github
聊天室 WebSocket+Vue
??HTTP是不支持長連接的,WebSocket是一種通信協(xié)議,提供了在單一、長連接上進(jìn)行全雙工通信的方式。它被設(shè)計(jì)用于在Web瀏覽器和Web服務(wù)器之間實(shí)現(xiàn),但也可以用于任何需要實(shí)時(shí)通信的應(yīng)用程序。使用ws作為協(xié)議標(biāo)識(shí)符,如果需要加密則使用wss作為協(xié)議標(biāo)識(shí)符,類似于http和https的區(qū)別。
相比HTTP,WebSocket請求頭多了
??????Upgrade: websocket
??????Connection: Upgrade
這一段是表明現(xiàn)在是用的WebSocket協(xié)議,而不是簡單的HTTP。
優(yōu)勢:
- 全雙工通信: 與傳統(tǒng)的請求-響應(yīng)通信模型(如HTTP)不同,WebSocket允許雙向通信??蛻舳撕头?wù)器都可以獨(dú)立地發(fā)送消息。
- 持久連接:WebSocket在客戶端和服務(wù)器之間建立了一個(gè)持久連接,只要雙方需要,連接就會(huì)保持打開狀態(tài)。這消除了為每次通信重復(fù)打開和關(guān)閉連接的需要。
- 低延遲:WebSocket旨在最小化延遲并減少傳統(tǒng)HTTP輪詢所帶來的開銷。這使其非常適用于需要實(shí)時(shí)更新的應(yīng)用程序,例如聊天應(yīng)用程序、在線游戲、金融交易平臺(tái)和協(xié)同編輯工具。
為建立WebSocket連接,客戶端向服務(wù)器發(fā)送WebSocket握手請求,在握手成功后,連接將升級到WebSocket協(xié)議。一旦建立,客戶端和服務(wù)器都可以隨時(shí)相互發(fā)送消息。
這也就是最大的一個(gè)特點(diǎn):服務(wù)器可以主動(dòng)向客戶端推送信息,客戶端也可以主動(dòng)向服務(wù)器發(fā)送信息,是真正的雙向平等對話。
WebSocket適用于需要實(shí)時(shí)更新或連續(xù)的數(shù)據(jù)流,而HTTP更適用于獲取舊數(shù)據(jù)或者只獲得一次數(shù)據(jù)即可,不需要實(shí)時(shí)獲取數(shù)據(jù)。
綜上,WebSocket適用于需要實(shí)時(shí)的場景,例如實(shí)時(shí)聊天室、多玩家游戲、實(shí)時(shí)地圖位置、在線協(xié)同編輯等場景,接下來主要運(yùn)用于實(shí)時(shí)聊天室實(shí)現(xiàn)多人聊天室,可以做到將消息放松到群聊并實(shí)時(shí)推送給全體用戶。
前端
具體框架如下圖所示:
布局
頁面布局主要分上下兩部分,上面是導(dǎo)航欄,下面是登陸頁面或者聊天頁面。
在App.vue中進(jìn)行初次布局,按照上圖進(jìn)行設(shè)計(jì)如下
<ContentBase>
<NavBar />
</ContentBase>
<div class="box">
<router-view/>
</div>
由于下面會(huì)根據(jù)登陸狀態(tài)分LoginView.vue和HomeView.vue兩種頁面,所以需要設(shè)置為路由狀態(tài),根據(jù)路由選擇顯示指定頁面。
對每一個(gè)方塊可以設(shè)計(jì)個(gè)卡片包括起來,主要使用bootstrap
封裝到ContentBase.vue 使用 接收傳過來的children,之后只要把需要"框"起來只需要放到即可。
<template>
<div class="home">
<div class="container">
<div class="card">
<div class="card-body">
<slot></slot>
<!-- 存放父組件傳過來的children -->
</div>
</div>
</div>
</div>
</template>
導(dǎo)航欄
首先是導(dǎo)航欄,根據(jù)登陸狀態(tài)會(huì)有兩種情況,當(dāng)未登陸的時(shí)候,分為(聊天室,Home,登陸)。無論點(diǎn)擊哪一個(gè)都自己路由到登陸頁面,當(dāng)?shù)顷懙臅r(shí)候分為(聊天室,username,退出)
當(dāng)點(diǎn)擊username或聊天室會(huì)顯示登陸頁面,如果點(diǎn)擊退出會(huì)觸發(fā)logout函數(shù)使用戶退出并顯示登陸狀態(tài)。
其中l(wèi)ogout函數(shù)簡單介紹
const logout = () => {
store.commit('logout');
}
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<router-link v-if="$store.state.is_login" class="navbar-brand" :to="{name:'home'}">聊天室</router-link>
<router-link v-else class="navbar-brand" :to="{name:'login'}">聊天室</router-link>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav" v-if="!$store.state.is_login">
<li class="nav-item">
<router-link class="nav-link active" aria-current="page" :to="{name:'login'}">Home</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name:'login'}" role="button" data-bs-toggle="dropdown" aria-expanded="false">
登陸
</router-link>
</li>
</ul>
<!-- 上面是未登錄狀態(tài) 下面是已登錄狀態(tài) -->
<ul class="navbar-nav" v-else>
<li class="nav-item">
<router-link class="nav-link active" aria-current="page" :to="{name:'home'}">{{ $store.state.username }}</router-link>
</li>
<li class="nav-item">
<router-link @click="logout" class="nav-link" :to="{name:'login'}" role="button" data-bs-toggle="dropdown" aria-expanded="false">
退出
</router-link>
</li>
</ul>
</div>
</div>
</nav>
</div>
接下來就是下面主頁面,分為登錄頁面和聊天頁面,所以需要路由
在component不適用引入而是直接使用匿名函數(shù)是異步加載方式即頁面沒有被顯示這個(gè)代碼就不會(huì)被執(zhí)行
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "about" */ '../views/LoginView.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
登陸頁面
“/login” 是登陸頁面
在這里簡單設(shè)計(jì)一下登錄窗口即可
<ContentBase style="margin-top: 200px;;">
<form>
<div class="mb-3">
<input type="text" placeholder="請輸入用戶名" v-model="username" />
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Check me out</label>
</div>
<button type="submit" class="btn btn-primary" @click="handleEnter">Submit</button>
</form>
</ContentBase>
當(dāng)submit之后會(huì)觸發(fā)handleEnter函數(shù),在script中進(jìn)行聲明
const handleEnter = () => {
const _username = username.value; //ref對象的值用value取得
//控制用戶名格式
if (_username.length < 2) {
alert('用戶名應(yīng)不小于2位');
username.value = '';
return;
}
sessionStorage.setItem('username', _username); //存入localStorage
username.value = '';
store.commit('login',_username);
router.push('/');
};
用戶名必須要不小于兩位,確認(rèn)成功之后存入到sessionStorage中并且調(diào)用store.commit上傳到vuex中,這樣就可以全局改變登陸狀態(tài),并且立馬路由到聊天頁面上。vuex的配置會(huì)在后續(xù)給出。
如果需要根據(jù)之前是否有所登錄,如果有登錄可以在掛載之后檢查sessionStorage檢查是否存在,如果存在立馬跳轉(zhuǎn),比如下列代碼
onMounted(()=>{
//查看sessionStorage中是否存在username
username.value = sessionStorage.getItem('username');
if(username.value){//存在的話直接跳轉(zhuǎn)
router.push('/');
return ;
}
});
sessionStorage是位置
聊天頁面
“/” 是聊天頁面
聊天頁面分成三塊,左側(cè)是用戶列表UserList,右側(cè)分上下兩層分別為聊天記錄框和發(fā)送窗口
在用戶列表中會(huì)顯示當(dāng)前在線用戶人數(shù)以及用戶名稱,對用戶列表會(huì)排除掉自身,因?yàn)樽陨頃?huì)放到首位顯示出。
為了簡單起見就不再分三個(gè)components,直接在HomeView.vue中設(shè)計(jì)下布局即可,使用bootstrap中的grid布局。
左側(cè)右側(cè)col-3 col-9即3:9分開,上下大概550px:150px。
<template>
<ContentBase>
<div class="row" style="width: 700px;">
<div class="col-3" style="width:150px; background-color: lightblue;">
<div>UserList : {{ userList.length+1 }} </div>
<hr>
<div>{{ username }}</div>
<div v-for="(item,index) in userList" :key="index"> {{ item }} </div>
</div>
<div class="col-9" style="height:500px; width: 550px; padding: 0px 0px;">
<div id="ltk">
<ul>
<li v-for="item of msgList" :key="item.id" style="list-style: none; height: 80px;">
<div>
<span style="display: block; text-align: center; font-size: small;">{{ item.dataTime }}</span>
<span v-if="item.user === username"
style="display: block; text-align: right; padding-right: 50px; padding-top: 0; font-size: small;">
{{ item.user }}
</span>
<span v-else style="text-align: left;">
{{ item.user }}
</span>
</div>
<div v-if="item.user === username"
style="display: block; text-align: right; padding-right: 50px;">
{{ item.msg }}
</div>
<div v-else style="text-align: left;">
{{ item.msg }}
</div>
</li>
</ul>
</div>
<div class="input">
<input type="text" placeholder="輸入消息" v-model="msg"/>
<button @click="handleSend">發(fā)送</button>
</div>
</div>
</div>
</ContentBase>
</template>
這部分script部分比較重要
需要實(shí)現(xiàn)的功能主要有:
獲取當(dāng)前username
保證實(shí)時(shí)滾動(dòng)即當(dāng)消息過多的時(shí)候需要發(fā)消息會(huì)滾動(dòng)到底層,使用scrollTop=scrollHeight即可(讓頂層和當(dāng)前高度一致)。
發(fā)送消息:需要向服務(wù)端發(fā)送,便于服務(wù)端群發(fā)到各個(gè)在線用戶,為了便于顯示當(dāng)前時(shí)間格式設(shè)置為"時(shí):分:秒",但要注意判斷不能全是空格,利用trim()去掉空格來判斷
為了便于獲取數(shù)據(jù),設(shè)置函數(shù)handleMessage來接收服務(wù)端廣播的消息,以便實(shí)時(shí)獲取到數(shù)據(jù)
為了便于新用戶獲得之前的聊天記錄,可以讓服務(wù)器記錄歷史記錄,新用戶初次登陸進(jìn)行獲取
<script>
import { reactive,toRefs,onMounted,ref } from 'vue';
import { useRouter } from 'vue-router';
import useWebSocket from '../hooks/websocket.js'
import store from '@/store';
import ContentBase from '@/components/ContentBase.vue';
import LoginView from './LoginView.vue';
export default {
name: 'HomeView',
components:{
ContentBase,
},
setup(){
const router = useRouter();
const state = reactive({
msg:'',
msgList:[],
});
let userList = ref([]);
let username = sessionStorage.getItem('username');
onMounted(() => {
if(!username || store.state.is_login===false){
router.push('/login');
return ;
}
//實(shí)現(xiàn)實(shí)時(shí)滾動(dòng)
setInterval(()=>{
var e = document.getElementById('ltk');
e.scrollTop = e.scrollHeight;
//scrollTop:指的是滾動(dòng)條卷去的距離(滾動(dòng)條向下滾動(dòng)之后距離頂部的距離)
//scrollHeight:指的是內(nèi)容的高度
},20)
})
const ws = useWebSocket(handleMessage,username);
const handleSend = () => {
const _msg = state.msg;
if(!_msg.trim().length){
return ;//空
}
let time = new Date();
ws.send(JSON.stringify({
id: time.getTime(),
user: username,
dataTime:`${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`,
msg:state.msg,
}));
state.msg = '';
}
function handleMessage(e){
const _msgData=JSON.parse(e.data);
if(_msgData.tag === 'userUpdate'){
for(let i = 0;i < _msgData.userList.length;i++){
var f = false;
for(let j = 0;j < userList.value.length;j++){
if(userList.value[j] === _msgData.userList[i]){
f = true;
break;
}
}
if(!f)
userList.value.push(_msgData.userList[i]);
}
}else if(_msgData.tag === 'msgUpdate'){
if(state.msgList.length === 0)
console.log(_msgData.chatList.length);
for(let i = 0;i < _msgData.chatList.length;i++){
state.msgList.push(_msgData.chatList[i]);
}
}
else
state.msgList.push(_msgData);
userList.value = userList.value.filter(item=>item!==username);
}
return {
...toRefs(state), //平鋪開state
handleSend,
userList,
username,
}
}
}
</script>
vuex中存儲(chǔ)當(dāng)前用戶的登陸狀態(tài)和當(dāng)前用戶名
state: {
is_login : false,
username : "",
// count : 0,
},
getters: {
},
mutations: {
login(state,username){
state.username = username,
state.is_login = true;
},
logout(state){
state.is_login = false;
state.username = "";
}
},
WebSocket
最關(guān)鍵的是WebSocket的實(shí)現(xiàn),WebSocket構(gòu)造函數(shù)開啟ws://localhost:8000端口,主要分為open、message、close、error。
當(dāng)處理open的時(shí)候,可以默認(rèn)發(fā)送I’m ${username} 作為問候語。
當(dāng)處理message的時(shí)候,調(diào)用handleMessage讓前端獲取到服務(wù)端廣播的消息。
當(dāng)處理close的時(shí)候,打印一下表明已經(jīng)退出即可。
當(dāng)處理error的時(shí)候,簡單打印下event。
function useWebSocket(handleMessage,username){
const ws = new WebSocket('ws://localhost:8000');
ws.addEventListener('open',(e)=>{
console.log('client open');
let time = new Date();
ws.send(JSON.stringify({
id: time.getTime(),
user: username,
dataTime:`${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`,
msg:`Hello I'm ${username}`,
}));
},false);
ws.addEventListener('message',handleMessage);
ws.addEventListener('close',(event)=>{
console.log(`${username} closed`);
});
ws.addEventListener('error',(event)=>{
console.log('error: ',event);
})
return ws;
}
export default useWebSocket;
后端
??ws后端,利用Server配置端口號(hào),監(jiān)聽事件。分為open、error、connect、close。只有當(dāng)connect成功的時(shí)候才會(huì)出現(xiàn)事件message。
主要對connect、message和close進(jìn)行相應(yīng)的處理。
??首先定義了userNum、chatList和userList記錄當(dāng)前在線人數(shù)、歷史群聊記錄和當(dāng)前用戶列表。
connect:當(dāng)觸發(fā)該事件時(shí),表明有了新客戶端進(jìn)行了連接,這時(shí)候可以增加當(dāng)前在線人數(shù)、username到userList并且廣播。
message:當(dāng)觸發(fā)該事件的時(shí)候,需要廣播該消息到每個(gè)客戶端并且廣播chatList,因?yàn)樾掠脩艏尤霑?huì)發(fā)送一個(gè)消息所以必定會(huì)立馬獲得chatList,當(dāng)然放到connect不放在message中也可以。
close:當(dāng)觸發(fā)該事件的時(shí)候,需要減少人數(shù)并且廣播。
這么多種廣播,客戶端應(yīng)該如何區(qū)分呢?可以在對象中增加tag屬性來表明廣播的是哪種類型的信息。(userUpdate、chatUpdate)
客戶端在handleMessage區(qū)分廣播的消息如下:
function handleMessage(e){
const _msgData=JSON.parse(e.data);
if(_msgData.tag === 'userUpdate'){
for(let i = 0;i < _msgData.userList.length;i++){
var f = false;
for(let j = 0;j < userList.value.length;j++){
if(userList.value[j] === _msgData.userList[i]){
f = true;
break;
}
}
if(!f)
userList.value.push(_msgData.userList[i]);
}
}else if(_msgData.tag === 'msgUpdate'){
if(state.msgList.length === 0)
console.log(_msgData.chatList.length);
for(let i = 0;i < _msgData.chatList.length;i++){
state.msgList.push(_msgData.chatList[i]);
}
}
else
state.msgList.push(_msgData);
userList.value = userList.value.filter(item=>item!==username);
}
var userNum = 0
var chatList = []
var userList = []
const WebSocket = require('ws');
const server = new WebSocket.Server({port:8000});
server.on('open',()=>{
console.log('server open');
})
server.on('error',()=>{
console.log('server error');
})
server.on('connection',(ws)=>{
console.log('has connected');
userNum++;
server.clients.forEach((item)=>{
if(item.readyState === WebSocket.OPEN)
item.send(JSON.stringify({tag:"userUpdate",userNum:userNum,userList:userList}));
})
ws.on('message',(msg)=>{
msg = msg.toString();
console.log(`received message ${msg}`);
var res = JSON.parse(msg);
server.clients.forEach((item)=>{
if(item.readyState === WebSocket.OPEN)
item.send(JSON.stringify({tag:"msgUpdate",chatList:chatList}));
})
chatList.push(res);
var f = false;
for(let i = 0;i < userList.length;i++)
if(userList[i] === res.user)
f = true;
if(!f){
userNum=server.clients.size;
userList.push(res.user);
server.clients.forEach((item)=>{
if(item.readyState === WebSocket.OPEN)
item.send(JSON.stringify({tag:"userUpdate",userNum:userNum,userList:userList}));
})
}
server.clients.forEach((client)=>{
if(client.readyState === WebSocket.OPEN)
client.send(msg);
})
})
})
server.on('close',()=>{
userNum--;
server.clients.forEach((item)=>{
if(item.readyState === WebSocket.OPEN)
item.send(JSON.stringify({tag:"userUpdate",userNum:userNum,userList:userList}));
})
console.log('disconnected');
})
設(shè)置下執(zhí)行語句在package.json
"scripts": {
"dev": "nodemon index.js"
},
執(zhí)行npm run dev 等效于 nodemon index.js 開啟服務(wù)器。
運(yùn)行結(jié)果
未登陸狀態(tài)
導(dǎo)航欄
登陸頁面
已登陸狀態(tài)
導(dǎo)航欄
聊天界面(實(shí)時(shí)群聊)
異步調(diào)用
在JS中,同步任務(wù)在主線程中,會(huì)優(yōu)先執(zhí)行,碰到異步任務(wù)就會(huì)放入到異步隊(duì)列中,而異步隊(duì)列又分為兩個(gè)隊(duì)列,分別是宏隊(duì)列和微隊(duì)列。
JS執(zhí)行時(shí)首先執(zhí)行所有的初始化同步任務(wù)的代碼,之后就會(huì)區(qū)分出宏隊(duì)列和微隊(duì)列執(zhí)行。每執(zhí)行一個(gè)宏任務(wù)之前都會(huì)執(zhí)行一個(gè)微任務(wù)。
宏隊(duì)列主要存儲(chǔ)宏任務(wù),包括定時(shí)器setTimeout、ajax回調(diào)等
微隊(duì)列主要存儲(chǔ)微任務(wù),包括Promise回調(diào)等
AJAX
作用:可以在不刷新的情況下向服務(wù)端發(fā)送http請求并得到響應(yīng)
最初使用XML格式的字符串,現(xiàn)在使用更加簡潔的JSON格式,設(shè)置xhr.responseType='json’即可實(shí)現(xiàn)。
Ajax操作主要分為四步:
創(chuàng)建對象、設(shè)置請求類型和url、發(fā)送請求、綁定事件
當(dāng)狀態(tài)為4的時(shí)候表明服務(wù)器完成請求響應(yīng),之后可以根據(jù)xhr.status判斷響應(yīng)狀態(tài)
//1. 創(chuàng)建對象
const xhr = new XMLHttpRequest();
//2. 設(shè)置類型和url
xhr.open('POST','http://127.0.0.1:8000/server');
//3. 發(fā)送
xhr.send();
//4. 綁定事件
xhr.onreadystatechange = ()=>{
if(xhr.readyState === 4){
// 判斷響應(yīng)狀態(tài)是否成功
if(xhr.status >= 200 && xhr.status < 300){
//對xhr.response進(jìn)行相關(guān)操作;
//xhr.response是響應(yīng)體
}
}
}
原生的AJAX一般很少直接用到,更多的是用jQuery、fetch或axios間接運(yùn)用AJAX。
Promise
Promise是ES6規(guī)范新技術(shù)、是實(shí)現(xiàn)異步編程的新解決方案。簡單來說Promise就是一個(gè)容器,里面保存著某個(gè)未來才會(huì)結(jié)束的結(jié)果,等結(jié)果出來之后才執(zhí)行相應(yīng)的回調(diào)函數(shù)從而實(shí)現(xiàn)異步操作。
Promise支持鏈?zhǔn)秸{(diào)用,可以解決回調(diào)地域問題,但是一旦建立就會(huì)立即執(zhí)行,無法發(fā)中途取消。
Promise主要有三種狀態(tài),分別是pending、fulfilled、rejected。初始為pending操作,執(zhí)行異步操作會(huì)執(zhí)行resolve()或reject()從而變成fulfilled狀態(tài)或rejected狀態(tài),之后就會(huì)執(zhí)行then()參數(shù)的成功或失敗的回調(diào)函數(shù)。
對于Promise的狀態(tài),只會(huì)發(fā)生pending->fulfilled或pending->rejected,即只會(huì)改變一次,但是可以指定多個(gè)回調(diào)
let p = new Promise((resolve,reject) => {
resolve('ok'); //pending->fulfilled
})
p.then(value=>{
console.log(value);
})
p.then(value=>{
alert(value);
})
then()方法本身也是返回Promise對象,可以使用const result = p.then(…)
既然返回Promise對象,那么返回的對象狀態(tài)是如何決定的?
拋出異?!顟B(tài)
非Promise類型——成功狀態(tài)且result和return結(jié)果保持一致
Promise對象——狀態(tài)和結(jié)果都和該P(yáng)romise對象一致
Promise還會(huì)有異常穿透,即進(jìn)行then()鏈?zhǔn)秸{(diào)用時(shí),可以在最后指定失敗回調(diào)。
p.then(value=>{
console.log(111);
}).then(value=>{
console.log(222);
}).then(value=>{
throw '!';
console.log(333);
}).catch(reason=>{
console.warn(reason);
})
如何中斷一個(gè)Promise鏈呢?
??在Promise鏈中,對于fulfilled狀態(tài)或者rejected狀態(tài),都會(huì)傳遞到后面,所以需要一個(gè)pending狀態(tài)的Promise對象才可以完成!
即
??return new Promise(()=>{})
async/await
是實(shí)現(xiàn)異步調(diào)用的一個(gè)手段,await必須放到async里面,await可以簡單理解為async wait,后面一般修飾promise對象,等待pending狀態(tài)被改變。
異步讀取三個(gè)文件
async function main(){
try{
let data1 = await mineReadFile('./files/1.html');
let data2 = await mineReadFile('./files/2.html');
let data3 = await mineReadFile('./files/3.html');
console.log(data1+data2+data3);
}catch(e){
console.log(e);
}
}
如何使用async和await來實(shí)現(xiàn)發(fā)送AJAX呢?
先將AJAX用Promise封裝起來得到sendAJAX函數(shù)
之后定義
async function(){
let data = await sendAJAX(url);
console.log(data);
}
將該函數(shù)綁定到某個(gè)觸發(fā)事件中即可
axios
本質(zhì)是基于promise的Ajax流行庫,使用了promise并且底層發(fā)送Ajax。
請求流程
當(dāng)使用axios返送請求時(shí),會(huì)調(diào)用request(config),內(nèi)部執(zhí)行dispatchRequest(config),dispatchRequest()會(huì)調(diào)用適配器xhrAdapter,執(zhí)行回調(diào)后并返回promise對象。
當(dāng)數(shù)據(jù)完成響應(yīng)后,會(huì)原路返回,最后根據(jù)響應(yīng)結(jié)果axios.then()執(zhí)行相應(yīng)的回調(diào)函數(shù)。
request(config)會(huì)將請求攔截器、響應(yīng)攔截器、dispatchRequest()進(jìn)行promise串聯(lián),這樣可以完成攔截的功能。
在內(nèi)部主要存于chain數(shù)組中,只有經(jīng)過請求攔截器才會(huì)執(zhí)行dispatchRequest(),響應(yīng)結(jié)束后會(huì)執(zhí)行響應(yīng)攔截器,都返回成功才是真的成功。
dispatchRequest()會(huì)轉(zhuǎn)換請求數(shù)據(jù)和響應(yīng)數(shù)據(jù),并調(diào)用xhrAdapter()發(fā)送請求。
xhrAdapter()會(huì)創(chuàng)建XHR對象,發(fā)送Ajax請求。文章來源:http://www.zghlxwxcb.cn/news/detail-797200.html
取消流程
??當(dāng)需要取消請求的時(shí)候,我們可以通過執(zhí)行某個(gè)函數(shù)從而改變promise的狀態(tài),當(dāng)promise狀態(tài)從pending->fulfilled立馬執(zhí)行then的成功回調(diào),而這個(gè)成功回調(diào)會(huì)執(zhí)行xhr.abort(),所以可以暴露出來一個(gè)改變該promise對象狀態(tài)的函數(shù)即可完成取消請求。文章來源地址http://www.zghlxwxcb.cn/news/detail-797200.html
//取消請求
if(config.cancelToken){
//對cancelToken 對象身上的 promise對象指定成功的回調(diào)
config.cancelToken.promise.then(value=>{
xhr.abort();
})
//一直是pending狀態(tài) 一旦改變成fulfilled狀態(tài)立馬執(zhí)行回調(diào)
}
到了這里,關(guān)于WebSocket+Vue實(shí)現(xiàn)簡易多人聊天室 以及 對異步調(diào)用的理解的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!