PeterDr
PeterDr

Reputation: 43

Using async/await instead of callback when using Worklet

I'm writing a wrapper class hiding the internals of working with AudioWorklet. Working with a worklet involves communication between a node and a processor through message ports.

As soon as the code running in the node reaches port.postMessage(), script execution in the node ends. When node.port.onmessage fires (through processor.port.postMessage), code in the node can resume execution.

I can get it to work by using a callback function. See the code below.

class HelloWorklet {
    constructor(audioContext) {
        audioContext.audioWorklet.addModule('helloprocessor.js').then(() => {
            this.awNode = new AudioWorkletNode(audioContext, 'hello-processor');
            this.awNode.port.onmessage = (event) => {
                switch (event.data.action) {
                case 'response message':
                    this.respondMessage(event.data);
                    break;
                }
            }
        });
    }
    requestMessage = (callback) => {
        this.awNode.port.postMessage({action: 'request message'});
        this.callback = callback;
    }
    respondMessage = (data) => {
        // some time consuming processing
        let msg = data.msg + '!';
        this.callback(msg);
    }
}

let audioCtx = new AudioContext();
let helloNode = new HelloWorklet(audioCtx);

const showMessage = (msg) => {
    // additional processing
    console.log(msg);
}

const requestMessage = () => {
    helloNode.requestMessage(showMessage);
}

and the processor

class HelloProcessor extends AudioWorkletProcessor {
    constructor() {
        super();

        this.port.onmessage = (event) => {
            switch (event.data.action) {
            case 'request message':
                this.port.postMessage({action: 'response message', msg: 'Hello world'});
                break;
            }
        }
    }

    process(inputs, outputs, parameters) {
        // required method, but irrelevant for this question
        return true;
    }
}
registerProcessor('hello-processor', HelloProcessor);

Calling requestMessage() causes Hello world! to be printed in the console. As using callbacks sometimes decreases the readability of the code, i'd like to rewrite the code using await like so:

async requestMessage = () => {
    let msg = await helloNode.requestMessage;
    // additional processing
    console.log(msg);
}

Trying to rewrite the HelloWorklet.requestMessage I cannot figure out how to glue the resolve of the Promise to the this.awNode.port.onmessage. To me it appears as if the interruption of the code between this.awNode.port.postMessage and this.awNode.port.onmessage goes beyond a-synchronicity.

As using the AudioWorklet already breaks any backwards compatibility, the latest ECMAScript features can be used.

edit

Thanks to part 3 of the answer of Khaled Osman I was able to rewrite the class as follows:

class HelloWorklet {
    constructor(audioContext) {
        audioContext.audioWorklet.addModule('helloprocessor.js').then(() => {
            this.awNode = new AudioWorkletNode(audioContext, 'hello-processor');
            this.awNode.port.onmessage = (event) => {
                switch (event.data.action) {
                case 'response message':
                    this.respondMessage(event.data);
                    break;
                }
            }
        });
    }
    requestMessage = () => {
        return new Promise((resolve, reject) => {
            this.resolve = resolve;
            this.reject = reject;
            this.awNode.port.postMessage({action: 'request message'});
        })
    }
    respondMessage = (data) => {
        // some time consuming processing
        let msg = data.msg + '!';
        this.resolve(msg);
    }
}

let audioCtx = new AudioContext();
let helloNode = new HelloWorklet(audioCtx);

async function requestMessage() {
    let msg = await helloNode.requestMessage();
    // additional processing
    console.log(msg);
}

Upvotes: 4

Views: 581

Answers (1)

Khaled Osman
Khaled Osman

Reputation: 1467

I think there're three things that might help you

  1. Promises don't return multiple values, so something like request message can not be fired again once its fulfilled/resolved, so it won't be suitable to request/post multiple messages. For that you can use Observables or RxJS

  2. You can use util.promisify to convert NodeJS callback style functions to promises like so

const { readFile } = require('fs')
const { promisify } = require('util')
const readFilePromise = promisify(fs.readFile)

readFilePromise('test.txt').then(console.log)

or manually create wrapper functions that return promises around them that resolve/reject inside the callbacks.

  1. For resolving a promise outside of the promise's block you can save the resolve/reject as variables and call them later like so
class MyClass {
  requestSomething() {
    return new Promise((resolve, reject) => {
      this.resolve = resolve
      this.reject = reject
    })
  }

  onSomethingReturned(something) {
    this.resolve(something)
  }
}

Upvotes: 1

Related Questions