前面是廢話戒良,后面寫了點(diǎn)代碼,有興趣看的可以點(diǎn)擊直達(dá)
(點(diǎn)擊后會跳轉(zhuǎn)到新 tab 頁冠摄,位置是對的糯崎,編輯器不支持當(dāng)前 tab 跳轉(zhuǎn)不好意思啦。)
近期一直在用 iisnode+koa2河泳,想起之前有個(gè)小項(xiàng)目(類似聊天室)后端用的是 php 實(shí)現(xiàn)的長輪詢沃呢。本人由于是個(gè) php 渣渣,項(xiàng)目完成的非常粗糙拆挥,就動(dòng)了重構(gòu)的念頭薄霜。
不得不承認(rèn)的是,開始的本意是想 nodejs 嘗試 websock 改造纸兔,但是先后試了 ws和 socket.io 后惰瓜,我驚(jue)喜(wang)地發(fā)現(xiàn),項(xiàng)目沒有問題汉矿,本地測試也非常順利崎坊,確實(shí)是很優(yōu)雅很美好的通信方式(改天有空記錄一下 koa 框架折騰 websocket 的經(jīng)歷)。然而部署到線上之后洲拇,一切都沒法正常運(yùn)行了奈揍,原因是無法建立 websocket 連接。
納尼赋续?
嗯打月,我們線上服務(wù)器是 window server 2008
iisnode 的作者寫的很清楚,兩篇文章無論是使用 faye-websocket 還是 socket.io 蚕捉,首段都寫了需要 iis8 以上,即系統(tǒng)應(yīng)該是 window 8 或者 window server 2012 以上才可以使用柴淘。原因有兩個(gè)
- websocket 的支持需要
.Net 4.5
版本以上 (這個(gè)安裝倒是可以想辦法解決) - 系統(tǒng)的核心組件
HTTP.SYS
需要在這兩個(gè)系統(tǒng)以上才支持 websocket
另外迫淹,社區(qū)還是有一些在低版本系統(tǒng)搭建 websocket 服務(wù)器的第三方庫的秘通,只是去解決這些問題怕是會碰到更棘手的狀況。如果有朋友有搭建經(jīng)驗(yàn)的敛熬,希望能分享一下~
事已至此肺稀,只好退而求其次,嘗試使用長輪詢方案來解決吧应民。
結(jié)合之前 php 實(shí)現(xiàn)的經(jīng)驗(yàn)梳理了一下代碼邏輯话原,整體思路應(yīng)該比較清晰
- 客戶端發(fā)起請求,使用原生的 XMLHttpRequest 或者 ajax 都可以诲锹,這里我用的是原生的XHR(不想為了ajax引入 jQuery或 zepto)
- 服務(wù)端接收到請求后繁仁,向數(shù)據(jù)庫查詢數(shù)據(jù),沒有數(shù)據(jù)時(shí)归园,進(jìn)入一個(gè)定時(shí)器查詢黄虱,有數(shù)據(jù)返回?zé)o數(shù)據(jù)則等到客戶端超時(shí)發(fā)起下一次輪詢請求
- 無數(shù)據(jù)返回時(shí),客戶端超時(shí)后再發(fā)起請求渺尘,有數(shù)據(jù)返回處理完數(shù)據(jù)权她,改變參數(shù)后發(fā)起一個(gè)新的請求猾编。
這里是 <a id="myexamplecode">我的</a> 代碼
//client.js 輪詢監(jiān)聽最新的消息
// url : '/api/msg/new/' + lastid (索引,建議存儲在 sessionStorage 中朱灿。
function listeningNewMsg() {
let lastId = sessionStorage.getItem('lastId')
let fetchUrl = `/api/msg/new/${lastId}`
let xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState== 4) {
if (xhr.status == 200) {
let res = JSON.parse(xhr.responseText)
//... dosomething
let newId = getNewId() //這里根據(jù)你的實(shí)際情況改變下一次請求的參數(shù)
sessionStorage.setItem('lastId', newId)
xhr = null //回收
listeningNewMsg()
}
}
}
xhr.onerror = (err) => {
console.error(err)
return;
}
xhr.ontimeout = () => {
xhr = null
listeningNewMsg()
}
xhr.timeout = 20000
xhr.open('GET', fetchUrl)
xhr.setRequestHeaders('Accept', 'application/json, text/plain')
xhr.send()
}
監(jiān)聽地址使用了我自己理解的 Restful 設(shè)計(jì),具體的自行設(shè)計(jì)
下面是服務(wù)端的代碼钠四,一個(gè)路由的中間件
//server
// nodeSql.fetchNewMsg 數(shù)據(jù)庫查詢方法 @return Promise
let timer = null
const ListeningMsg = async (ctx, next) => {
let lastId = ctx.params.lastId
let result = await nodeSql.fetchNewMsg(lastId)
if ( result.length <= 0 ) {
result = await longPollingNewMsg(lastId)
}
if ( timer ) {
clearTimeout(timer)
}
ctx.status = 200
ctx.type = 'application/json'
ctx.body = result
}
function longPollingNewMsg (lastId) {
return nodeSql.fetchNewMsg(lastId)
.then(result=>{
if (result.length > 0) {
return result
} else {
return Promise.reject('no new')
}
})
.catch(err=> {
console.log(err) // 'no new'
return new Promise(resolve => {
timer = setTimeout(() => resolve(), 500)
})
.then(()=>longPollingNewMsg(lastId))
})
}
這樣代碼就能工作了盗扒。
結(jié)語
這段代碼中最主要的是 longPollingNewMsg
方法,里面涉及了 promise 遞歸自身的問題形导。一直以來對 promise 的返回值都沒有特別明確的概念环疼,return
到底返回的是什么東西自己一直沒有特別清晰。經(jīng)過這次代碼的驗(yàn)證朵耕,對 promise 多了幾點(diǎn)認(rèn)識
- 在 promise 函數(shù)中執(zhí)行 return 炫隶,返回值會被下一個(gè) then 方法接收,如果沒有 then 方法阎曹,則返回到外部伪阶。
- 執(zhí)行 promise 函數(shù)應(yīng)該遵循 promise 的概念, 執(zhí)行一段代碼后只有兩種結(jié)果处嫌,要么是 resolved栅贴, 要么是 rejected 。
- 如果需要遞歸執(zhí)行 promise 函數(shù)熏迹,應(yīng)當(dāng)先結(jié)束當(dāng)前 promise (修改其 pending 狀態(tài)為 rejected)檐薯,在 catch 代碼中繼續(xù)調(diào)用
說的不對的地方請多多指教,謝謝~