小技巧:页面内全局通信、宏任务等待

27 阅读1分钟

一般 postMessage 用于不同页面窗口间通信,但在同一个页面内也是可以使用的。

window.addEventListener('message', e => {
  if (
    e.origin === location.origin &&
    e.source === e.target &&
    e.data === 'test'
  ) {
    console.log(2)
  }
})

出于安全考虑,判断是当前页面内(同一个页面同一个窗口)才执行相关逻辑:

  • e.origin === location.origin 同源
  • e.source === e.target 同窗口
console.log(1)
window.postMessage('test', location.origin)
console.log(3)

postMessage 第二个参数是 targetOrigin,消息也只发送给同源页面。

image.png

因为 postMessage 是宏任务,所以打印顺序是 1、3、2 。

那想按顺序打印 1、2、3 怎么办,中间加一个宏任务等待。

window.addEventListener('message', e => {
  if (
    e.origin === location.origin &&
    e.source === e.target &&
    e.data === 'test'
  ) {
    console.log(2)
  }
})
console.log(0)
window.postMessage('test', location.origin)
console.log(1)
await new Promise(r => setTimeout(r))
console.log(3)

await new Promise(r => setTimeout(r)) 一行代码实现宏任务等待,setTimeout 也是宏任务,执行以上代码时,任务队列中 setTimeoutpostMessage 后面,利用 awaitPromise 实现等待。

image.png

另一种变体:postMessage 也可以放到 Promise 里,效果是一样的。

console.log(1)
await new Promise(r => {
  window.postMessage('test', location.origin)
  setTimeout(r)
})
console.log(3)

image.png

其实还可以有一种变体,既然 postMessage 是宏任务,那也可以不需要 setTimeout,只通过 postMessage 来实现宏任务等待,因为是同一个窗口,可以借助 window 全局变量来搭个桥,存一下 Promiseresolve,消息接收方在合适的时机执行这个回调。

window.addEventListener('message', e => {
  if (
    e.origin === location.origin &&
    e.source === e.target &&
    e.data === 'test'
  ) {
    console.log(2)
    if (typeof window.waitResolve === 'function') {
      window.waitResolve() // Promise 的 resolve
      delete window.waitResolve
    }
  }
})
console.log(1)
await new Promise(r => {
  window.waitResolve = r
  window.postMessage('test', location.origin)
})
console.log(3)

image.png

顺序打印 1、2、3

以上!