找回密码
 立即注册
搜索

技术分享 【技术征文】在网页与快应用间构造可靠通信的通道

4
回复
4324
查看
  [复制链接]

2

主题

37

帖子

195

积分

 楼主| 2018-10-31 11:54:03 显示全部楼层 |阅读模式
本帖最后由 kongkongye 于 2018-10-31 16:42 编辑
信息
快应用版本: 1020
背景
在使用web组件进行快应用与网页间的通信的时候,有这几个发现:
  • 运行'npm run watch'时会提示web组件不支持message事件,但实际上是支持的(只是提示上的小问题)
  • 快应用发信息到网页,与网页发信息到快应用,用的方法名都是postMessage,但数据格式并不一致,使用起来不方便
  • 消息会丢失(比如网页端消息处理函数还没执行到就给网页发信息等情况)
  • 网页给快应用发信息时,快应用发送给网页的信息才会带回
特点
因此,这里花了点力气,做了个辅助的库,特点:
  • 消息有序发送
  • 消息不会丢失,保证到达
  • 消息不会接收多次(去重)
  • 消息分类型,类似事件的监听机制,更加方便
快应用端代码
快应用里,新建一个channel.js文件,内容:
  1. /**
  2. * 通道,用来与html进行可靠的通信
  3. *
  4. * 1. 有序
  5. * 2. 回传与ack机制,保证到达
  6. * 3. id验证机制,去重
  7. *
  8. * @param thisObj {Object} this对象
  9. * @param webId {String} web的id
  10. * @param timeout {Number} 超时时间,超时后会重试,单位ms,默认1000
  11. */
  12. var Channel = function (thisObj, webId, timeout) {
  13. timeout = timeout || 1000
  14. var that = this
  15. var debug = (...msgs) => console.debug.apply(null, ['[通道]', ...msgs])
  16. var log = (...msgs) => console.log.apply(null, ['[通道]', ...msgs])
  17. var error = (...msgs) => console.error.apply(null, ['[通道]', ...msgs])
  18. //消息id生成器
  19. that.idGenerator = 0
  20. //消息发送队列
  21. that.sendQueue = [
  22. // {
  23. // data: {},
  24. // resolve: Object,
  25. // },
  26. ]
  27. //***列表
  28. that.listeners = {
  29. // type: [listener1, listener2],
  30. }
  31. //当前接收的消息id
  32. that.receivedId = 0
  33. /**
  34. * @param type {String} 消息类型
  35. * @param data {Object} 消息内容
  36. * @return {Promise}
  37. */
  38. that.sendMsg = function (type, data) {
  39. var resolve;
  40. var promise = new Promise(resolve_ => resolve = resolve_)
  41. that.sendQueue.push({
  42. data: {
  43. pType: 'msg',
  44. id: ++that.idGenerator,
  45. type: type,
  46. data: data,
  47. },
  48. resolve
  49. })
  50. return promise
  51. }
  52. /**
  53. * 监听
  54. * @param type 消息类型
  55. * @param callback 回调
  56. */
  57. that.on = function (type, callback) {
  58. var typeListeners = that.listeners[type] || []
  59. that.listeners[type] = typeListeners
  60. typeListeners.push(callback)
  61. }
  62. /**
  63. * 收到消息时调用(设置web组件的message事件的处理函数)
  64. */
  65. that.onMsg = function ({message, url}) {
  66. debug('[收到消息]', message, '('+url+')')
  67. var {pType, id, type, data} = JSON.parse(message)
  68. if (pType === 'ack') {//ack包
  69. if (id === that.idGenerator) {//是当前的ack,有效,删除元素
  70. var nowPackets = that.sendQueue.splice(0, 1);
  71. that.valid_(nowPackets.length === 1)
  72. nowPackets[0].resolve()
  73. }
  74. }else if (pType === 'msg') {//正常消息
  75. if (id === that.receivedId+1) {//是下个准备接收的包,有效
  76. //更新缓存值
  77. that.receivedId++
  78. //处理
  79. var typeListeners = that.listeners[type] || []
  80. log('[处理消息]', '类型: '+type, '处理器数量: '+typeListeners.length)
  81. for (var i=0;i<typeListeners.length;i++) {
  82. try {
  83. typeListeners[i](data)
  84. } catch (e) {
  85. error('[处理器错误]', e)
  86. }
  87. }
  88. //响应ack
  89. that.send_({
  90. pType: 'ack',
  91. id,
  92. })
  93. }
  94. }else {//没有pType,当作ping包处理,忽略
  95. return
  96. }
  97. }
  98. /**
  99. * 发包
  100. */
  101. that.send_ = function (packet) {
  102. var str = JSON.stringify(packet)
  103. thisObj.$element(webId).postMessage({
  104. message: str
  105. })
  106. debug('[发送消息]', str)
  107. }
  108. /**
  109. * 下个包
  110. */
  111. that.next_ = function() {
  112. if (that.sendQueue.length > 0) {
  113. that.send_(that.sendQueue[0].data)
  114. }
  115. }
  116. /**
  117. * 验证
  118. */
  119. that.valid_ = function(bool, errMsg) {
  120. if (!bool) {
  121. throw new Error(errMsg || 'Valid Fail!')
  122. }
  123. }
  124. //计时器: 不断重试发送包
  125. var timerId = setInterval(function () {
  126. if (thisObj.$valid) {//仍然有效
  127. that.next_()
  128. }else {//取消计时器
  129. log('已经失效,取消计时器')
  130. clearInterval(timerId)
  131. }
  132. }, timeout)
  133. }
  134. module.exports = Channel
复制代码
快应用端使用方式(参考):
  1. <template>
  2. <web id="web" src="xxx" @message="{{onMessage}}"></web>
  3. </template>
  4. <script>
  5. import Channel from './channel'
  6. export default {
  7. channel: null,
  8. onInit() {
  9. this.channel = new Channel(this, 'web')
  10. //通道监听
  11. this.channel.on('type1', function (data) {
  12. //处理收到的数据
  13. })
  14. //发送信息
  15. this.channel.sendMsg('type2', {
  16. //数据
  17. })
  18. },
  19. onMessage(param) {
  20. this.channel.onMsg(param)
  21. },
  22. }
  23. </script>
复制代码
网页端代码
网页端js(可以新建一个js或复制到script脚本里):
  1. /**
  2. * (在html端,实际上一个页面只能new一个通道)
  3. *
  4. * 通道,用来与html进行可靠的通信
  5. *
  6. * 1. 有序
  7. * 2. 回传与ack机制,保证到达
  8. * 3. id验证机制,去重
  9. *
  10. * @param logger {Function} 日志记录器,可为null
  11. * @param timeout {Number} 超时时间,超时后会重试,单位ms,默认1000
  12. */
  13. var Channel = function (logger, timeout) {
  14. timeout = timeout || 1000
  15. if (!logger) logger = () => {}
  16. var that = this
  17. //消息id生成器
  18. that.idGenerator = 0
  19. //消息发送队列
  20. that.sendQueue = [
  21. // {
  22. // data: {},
  23. // resolve: Object,
  24. // },
  25. ]
  26. //***列表
  27. that.listeners = {
  28. // type: [listener1, listener2],
  29. }
  30. //当前接收的消息id
  31. that.receivedId = 0
  32. /**
  33. * @param type {String} 消息类型
  34. * @param data {Object} 消息内容
  35. * @return {Promise}
  36. */
  37. that.sendMsg = function (type, data) {
  38. var resolve;
  39. var promise = new Promise(resolve_ => resolve = resolve_)
  40. that.sendQueue.push({
  41. data: {
  42. pType: 'msg',
  43. id: ++that.idGenerator,
  44. type: type,
  45. data: data,
  46. },
  47. resolve
  48. })
  49. return promise
  50. }
  51. /**
  52. * 监听
  53. * @param type 消息类型
  54. * @param callback 回调
  55. */
  56. that.on = function (type, callback) {
  57. var typeListeners = that.listeners[type] || []
  58. that.listeners[type] = typeListeners
  59. typeListeners.push(callback)
  60. }
  61. /**
  62. * 收到消息时调用(设置为onmessage的处理函数)
  63. */
  64. that.onMsg = function (message) {
  65. logger('[收到消息]' + message)
  66. var {pType, id, type, data} = JSON.parse(message)
  67. if (pType === 'ack') {//ack
  68. if (id === that.idGenerator) {//是当前的ack,有效,删除元素
  69. var nowPackets = that.sendQueue.splice(0, 1);
  70. that.valid_(nowPackets.length === 1)
  71. nowPackets[0].resolve()
  72. }
  73. } else if (pType === 'msg') {//正常消息
  74. if (id === that.receivedId + 1) {//是下个准备接收的包,有效
  75. //更新缓存值
  76. that.receivedId++
  77. //处理
  78. var typeListeners = that.listeners[type] || []
  79. logger('[处理消息]类型: ' + type + ' 处理器数量: ' + typeListeners.length)
  80. for (var i = 0; i < typeListeners.length; i++) {
  81. try {
  82. typeListeners[i](data)
  83. } catch (e) {
  84. logger('[处理异常]'+JSON.stringify(e))
  85. }
  86. }
  87. //响应ack
  88. that.send_({
  89. pType: 'ack',
  90. id,
  91. })
  92. }
  93. } else {//没有pType,忽略
  94. return
  95. }
  96. }
  97. /**
  98. * 发包
  99. */
  100. that.send_ = function (packet) {
  101. var str = JSON.stringify(packet)
  102. system.postMessage(str)
  103. logger('[发送消息]' + str)
  104. }
  105. /**
  106. * 下个包
  107. */
  108. that.next_ = function () {
  109. if (that.sendQueue.length > 0) {
  110. that.send_(that.sendQueue[0].data)
  111. }
  112. }
  113. /**
  114. * 验证
  115. */
  116. that.valid_ = function (bool, errMsg) {
  117. if (!bool) {
  118. throw new Error(errMsg || 'Valid Fail!')
  119. }
  120. }
  121. //计时器: 不断重试发送包
  122. setInterval(function () {
  123. that.next_()
  124. }, timeout)
  125. //计时器: ping
  126. //(必须不断地ping,因此html不发请求到app,那么app发送给html的消息就不会过来,蛋疼)
  127. setInterval(function () {
  128. that.send_({})
  129. }, 200)
  130. //对接
  131. system.onmessage = that.onMsg
  132. }
复制代码
网页端使用方法(参考):
  1. var channel = new Channel()
  2. //通道监听
  3. channel.on('type1', function (data) {
  4. //处理收到的数据
  5. })
  6. //发送信息
  7. channel.sendMsg('type2', {
  8. //数据
  9. })
复制代码
PS
  • 快应用发信息到网页,官方文档写的信息格式是messageString,但这个东西文档里并没说格式是怎样的,也搜不到,最后在官方的demo里,注释的代码里发现格式是{message: 'xxx'},非常坑爹
  • 网页端的日志不好显示
回复

使用道具 举报

479

主题

780

帖子

6620

积分

2018-10-31 14:30:19 显示全部楼层
感谢楼主分享 已经收录啦~~
官方客服微信:kuaiyingyongguanKF
回复

使用道具 举报

3

主题

15

帖子

90

积分

2018-11-1 22:36:28 显示全部楼层
感谢楼主分享,只不过这个你的这个构造函数,看得我头疼,希望楼主规范下代码。
回复

使用道具 举报

2

主题

37

帖子

195

积分

 楼主| 2018-11-2 11:37:46 显示全部楼层
这叫什么事啊 发表于 2018-11-1 22:36 感谢楼主分享,只不过这个你的这个构造函数,看得我头疼,希望楼主规范下代码。 ...
不是很精通前端,望指出代码里不规范的地方
回复

使用道具 举报

0

主题

7

帖子

35

积分

2018-11-26 17:43:26 显示全部楼层
不错
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册