koa2长轮询--一次promise递归的个人实践

前面是废话,后面写了点代码,有兴趣看的可以点击直达
(点击后会跳转到新 tab 页,位置是对的,编辑器不支持当前 tab 跳转不好意思啦。)

近期一直在用 iisnode+koa2,想起之前有个小项目(类似聊天室)后端用的是 php 实现的长轮询。本人由于是个 php 渣渣,项目完成的非常粗糙,就动了重构的念头。

不得不承认的是,开始的本意是想 nodejs 尝试 websock 改造,但是先后试了 ws和 socket.io 后,我惊(jue)喜(wang)地发现,项目没有问题,本地测试也非常顺利,确实是很优雅很美好的通信方式(改天有空记录一下 koa 框架折腾 websocket 的经历)。然而部署到线上之后,一切都没法正常运行了,原因是无法建立 websocket 连接。

纳尼?

嗯,我们线上服务器是 window server 2008

iisnode 的作者写的很清楚,两篇文章无论是使用 faye-websocket 还是 socket.io ,首段都写了需要 iis8 以上,即系统应该是 window 8 或者 window server 2012 以上才可以使用。原因有两个

  1. websocket 的支持需要 .Net 4.5 版本以上 (这个安装倒是可以想办法解决)
  2. 系统的核心组件 HTTP.SYS 需要在这两个系统以上才支持 websocket

另外,社区还是有一些在低版本系统搭建 websocket 服务器的第三方库的,只是去解决这些问题怕是会碰到更棘手的状况。如果有朋友有搭建经验的,希望能分享一下~

事已至此,只好退而求其次,尝试使用长轮询方案来解决吧。
结合之前 php 实现的经验梳理了一下代码逻辑,整体思路应该比较清晰

  1. 客户端发起请求,使用原生的 XMLHttpRequest 或者 ajax 都可以,这里我用的是原生的XHR(不想为了ajax引入 jQuery或 zepto)
  2. 服务端接收到请求后,向数据库查询数据,没有数据时,进入一个定时器查询,有数据返回无数据则等到客户端超时发起下一次轮询请求
  3. 无数据返回时,客户端超时后再发起请求,有数据返回处理完数据,改变参数后发起一个新的请求。

这里是 我的 代码

//client.js 轮询监听最新的消息
// 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() //这里根据你的实际情况改变下一次请求的参数
          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()
  }

监听地址使用了我自己理解的 Restful 设计,具体的自行设计
下面是服务端的代码,一个路由的中间件

//server
// nodeSql.fetchNewMsg  数据库查询方法 @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))
    })
}

这样代码就能工作了。
结语


这段代码中最主要的是 longPollingNewMsg 方法,里面涉及了 promise 递归自身的问题。一直以来对 promise 的返回值都没有特别明确的概念,return 到底返回的是什么东西自己一直没有特别清晰。经过这次代码的验证,对 promise 多了几点认识

  1. 在 promise 函数中执行 return ,返回值会被下一个 then 方法接收,如果没有 then 方法,则返回到外部。
  2. 执行 promise 函数应该遵循 promise 的概念, 执行一段代码后只有两种结果,要么是 resolved, 要么是 rejected 。
  3. 如果需要递归执行 promise 函数,应当先结束当前 promise (修改其 pending 状态为 rejected),在 catch 代码中继续调用

说的不对的地方请多多指教,谢谢~

你可能感兴趣的:(koa2长轮询--一次promise递归的个人实践)