1

我正在尝试以双向方式与客户沟通服务工作者。我的目标是将请求的处理委托给客户端,并以适当的响应返回给服务人员。步骤顺序如下:

服务人员:

  1. Service Worker 收到请求
  2. 它检查它是否必须被忽略或正确管理
  3. 带有请求和相关 ID 的消息被创建
  4. 消息通过 Incoming 消息发送给客户端
  5. 对 {id: event} 在消息存储中注册

客户:

  1. 请求消息来自传入通道
  2. 消息被解构以获取相关 ID 和请求
  3. 为请求创建适当的响应
  4. 响应和相关 ID 通过 Outgoing Channel 发送回工作人员

服务人员:

  1. 响应消息来自传出通道
  2. 消息被解构以获取相关 ID 和响应
  3. 此 ID 的待处理消息是从消息存储中检索的
  4. 为响应创建一个新的 Response 对象
  5. 响应对象通过返回respondWith

这是我的服务人员:

const INSTALL   = 'install'
const ACTIVE    = 'activate'
const FETCH     = 'fetch'
const MESSAGE   = 'message'
const INSTALLED = 'Worker Installed'
const ACTIVATED = 'Worker Activated'
const FETCHING  = 'Worker fetching'
const ICHANNEL  = 'whale-ichannel'
const OCHANNEL  = 'whale-ochannel'
const HEADERS   = { 'Content-Type' : 'text/javascript' }
const PREFIX    = '/whale/'

let toJson   = JSON.stringify
let toJs     = JSON.parse
let Messages = new Map ()
let idx      = 0
let iChannel = new BroadcastChannel (ICHANNEL) // Incoming Channel
let oChannel = new BroadcastChannel (OCHANNEL) // Outgoing Channel

self.addEventListener (INSTALL, function (event) {

  console.log (INSTALLED)
  self.skipWaiting ()

})

self.addEventListener (ACTIVE, function (event) {

  console.log (ACTIVATED)
  event.waitUntil (clients.claim ())
  oChannel.addEventListener (MESSAGE, function ({ data }) {

    let id       = data.id
    let response = data.response
    let message  = Messages.get (id)

    Messages.delete (id)
    message.send (response)

  })
})

self.addEventListener (FETCH, async function (event) {

  if (isRequest (event)) await doRequest (event)

})

function isRequest (event) {
  let { request } = event
  let { url     } = request
  let uri  = new URL (url)
  let path = uri.pathname
  let ok   = path.startsWith (PREFIX)
  return ok
}

function getRequest (event) {
  let { request  } = event
  let { url      } = request
  let { referrer } = request
  let   headers    = toJs (toJson (request)) || {}

  return {
    url,
    referrer,
    headers
  }
}

function getResponse (data) {
  let headers  = HEADERS
  let text     = data
  let response = new Response (text, { headers })
  return response
}

function doRequest (event) {
  let request    = getRequest (event)
  let {id, wait} = getMessage (event)
  iChannel.postMessage ({ id, request })
  return wait
}

function getMessage (event) {
  let signal
  let wait = new Promise (function (ok) { signal = ok })
  let id   = idx++
  Messages.set (id, {
    send : function (data) {
      let response = getResponse (data)
      event.respondWith (response) // [1]
      signal (data)
    }
  })
  return { id, wait }
}

这是我的客户:

const ICHANNEL = 'whale-ichannel' // Incoming Channel
const OCHANNEL = 'whale-ochannel' // Outgoing Channel

// Register worker ...

let iChannel = new BroadcastChannel (ICHANNEL)
let oChannel = new BroadcastChannel (OCHANNEL)
iChannel.addEventListener (MESSAGE, async function ({ data }) {
  let { id      } = data
  let { request } = data
  let response    = MyFancyResponse (...)
  oChannel.postMessage ({
    id,
    response
  })
})

当我运行此代码时,会发出以下消息错误。请参阅 Service Worker 代码中的 [1]。请注意整个通信流程已正确执行。

Uncaught DOMException: Failed to execute 'respondWith' on 'FetchEvent': The event handler is already finished.

如果我没有误解的话,这意味着当工作人员被放弃去寻找客户端时,获取事件就完成了。所以我的方法是无效的。我的问题是:在请求-响应过程通过消息事件拆分的情况下,如何进行这种双向通信?

4

1 回答 1

1

我认为,问题在于 onFetch 事件需要立即响应 event.respondWith()

如果 onFetch 没有得到 event.respondWith,它会生成一个,并且你不能稍后传递你的响应,你会得到这个错误:事件处理程序已经完成。

所以,而不是

self.addEventListener (FETCH, async function (event) {
  if (isRequest (event)) await doRequest (event)
})

我会尝试

self.addEventListener (FETCH, function (event) {
    event.respondWith( ( async function() {
        if (isRequest (event)) return await doRequest (event); // note the RETURN
    }) () ); // note the (), what will call the inline async function to get a promise
})

and change the inner event.respondWith(xxx) to return xxx; 

请告诉,如果这解决了问题。

于 2021-02-14T20:22:27.300 回答