BroadcastChannel is losing .postMessage() context in setInterval function

294 Views Asked by At

This timing class wired from a Web Worker does not retain context for the BroadcastChannels postMessage() method. When in use it throws a error:

Uncaught TypeError: this.channel.postMessage is not a function

The error occurs when the emit() method calls the postMessage() method. Upon checking in the debugger, I can confirm that the this.channel object injected into the constructor is a valid BroadcastChannel instance and is also valid within the emit() method, as it contains the postMessage() method. However, despite these valid conditions, the error is still thrown.

Here is a debugger view: debugger view this.channel in the emit() method

I am using an arrow function so the this would respect the parent (FoozleTimer class) of the setInterval(); however it does not seem to work.

Here is the timing class, please note the .startTimer() and .emit() methods:

class FoozleTimer {
  timerInterval:any;
  channel:BroadcastChannel;

/**
 * Construct
 */
  constructor(channel:BroadcastChannel) {
    this.timerInterval = null;
    this.channel = channel;
  }

  /**
   * Time interval callback function
   */
  emit() {
    this.channel.postMessage({countBy:1})
    console.log('emit')
    return '';
  }

  startTimer(intervalTime = 1000) {
    this.timerInterval = setInterval(() => this.emit(), intervalTime);
  }

  stopTimer() {
    clearInterval(this.timerInterval);
  }

  getTimer() {
    return this.timerInterval;
  }
}

export default FoozleTimer;

Here the Web Worker where this timer is called:

const channel = new BroadcastChannel('channel2');
const controls = new BroadcastChannel('controls');
// Pass the Broadcast Channel into the timer
const FT = new FoozleTimer(channel);

FT.startTimer(1000);
const timer = FT.getTimer();

controls.onmessage = (event) => {
  if(event && event.data.msg == 'kill-timer') {
      FT.stopTimer();
      console.log('killed timer')
  }
}

The web worker is called like this in an index.html file:

const worker = new Worker("./TimerWorker2.js", { type: "module" });

Can you provide any insights as to why this wrong and potential ways to resolve it?

1

There are 1 best solutions below

0
On

I was able to fix this by using a closure instead of of the emit() method. I then set the context of this to a parameter self and can use this inside the closure. I also wrapped the BroadCast channel into a class wrapper so its' context couldn't change.

Here is the new timer class:

import { BroadcastService } from "./BroadcastService";

class FoozleTimer {
  timerInterval:any;
  broadcastService:BroadcastService;

/**
 * Construct
 */
  constructor(broadcastService:BroadcastService) {
    this.timerInterval = null;
    this.broadcastService = broadcastService;
  }

  startTimer(intervalTime = 1000) {
    const self:any = this;
    this.timerInterval = setInterval(function() {
      self.broadcastService.postMessage({countBy:1, status:"pass"})
    }, intervalTime);
}

  stopTimer() {
    clearInterval(this.timerInterval);
  }

  getTimer() {
    return this.timerInterval;
  }
}

export default FoozleTimer;

And here is the BroadcastService:

export class BroadcastService {
    broadcastChannel: BroadcastChannel;

    constructor(name:string) {
        this.broadcastChannel = new BroadcastChannel(name);
    }

    postMessage(msg:any) {
        this.broadcastChannel.postMessage(msg);
    }

    getBroadcastChannel() {
        return this.broadcastChannel;
    }
}

I will try to refactor the BroadcastService to not have the coupled BroadcastChannel(name) and instead inject a BroadcastChannel instance.

I hope this helps someone else