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);
}
I think there're three things that might help you
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
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.
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)
}
}
Related
Recently I was learning the source code of Axios(https://github.com/axios/axios).
However, in CancelToken.js, there is a part of code that I don't understand.
// eslint-disable-next-line func-names
this.promise.then = onfulfilled => {
let _resolve;
// eslint-disable-next-line func-names
const promise = new Promise(resolve => {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
token.unsubscribe(_resolve);
};
return promise;
};
Originally, I think it is used to overwrite the then method of this.promise. And then, I write a demo to verify my idea.
let resolve
let p = new Promise((res, rej) => resolve = res)
p.then(val => {
console.log('In p.then ', val)
})
p.then = onFulfill => {
console.log('I am here')
const promise = new Promise(resolve => {
resolve(2)
}).then(onFulfill)
return promise
}
console.log(p.then)
resolve(1)
If as what I expected, 'I am here' should be print. However, it can't print 'I am here'. So I don't understand the usage of this.promise.then = onFulfilled => ... in Axios source code.
Can anyone help me! THX!
Overwriting the method
You are correct, this does overwrite the then method of this Promise instance; the important thing here is when that happens.
If you consider this (extremely poor) resolve-only Promise implementation:
class Future {
constructor(executor) {
this._state = 'pending';
executor(result => {
if (this._state !== 'pending') {
return;
}
this._state = 'fulfilled';
if (!this._onFulfilled) {
return;
}
this._onFulfilled(result);
});
}
then(onFulfilled) {
this._onFulfilled = onFulfilled;
}
}
you can see that, if called like so:
const future = new Future(resolve => setTimeout(resolve, 1000));
future.then(() => console.log('fulfilled'));
future.then = console.log;
future.then('and then?');
reassigning then will leave the internal value of _onFulfilled untouched, and any subsequent calls to future.then will use the modified method.
In Axios?
In the Axios code, this appears to be used to allow using the CancelToken instance in two ways:
as an object owning a thenable, where the modified then appends the onFufilled handler to the internal _listeners array and returns a Promise, or
as a rough Observable, pushing the handler directly onto the _listeners array
So, mocking this up using the following setup:
let resolve;
const future = new Promise(res => resolve = res)
const listeners = [];
future.then(val => {
for (const listener of listeners) {
listener(val);
}
});
future.then = onFulfilled => new Promise(resolve => {
listeners.push(resolve);
}).then(onFulfilled);
the output of this:
for (let index = 0; index < 10; index++) {
future.then(val => console.log(`${index}: ${val}`));
}
is equivalent to that of this:
for (let index = 0; index < 10; index++) {
listeners.push(val => console.log(`${index}: ${val}`));
}
when kicked off with:
resolve(1);
I am trying to understand how promises work in JS by playing with swapi.dev. I would like to create a dynamic chain of promises (not using async/await) but it does not provide me with any result. In particular, the idea behind is to get all names of the given person (for instance Luke Skywalker) and dump them into the console.
Could anyone help me? What am I missing?
Thanks in advance.
"use strict";
const request = require("request-promise");
const BASE_URL = "http://swapi.dev/api";
var currentPromise = Promise.resolve();
callApiPromise(`${BASE_URL}/people/1`).then((data) => {
console.log("Getting vehicles' URLs");
const vehicles_URL = data["vehicles"];
console.log("Starting looping through URLs");
for (let i = 0; i < vehicles_URL.length; i++) {
console.log(`i=${i}, vehicle_URL=${vehicles_URL[i]}`);
currentPromise = currentPromise.then(function () {
console.log(".. getting vehicle name");
return getVehicleName[vehicles_URL[i]];
});
}
});
function getVehicleName(url) {
callApiPromise(url).then((vehicle_data) => {
var arrVehicleData = new Array();
arrVehicleData.push(vehicle_data);
console.log(arrVehicleData.map((vehicle) => vehicle.name));
});
}
function callApiPromise(url) {
return new Promise((resolve, reject) => {
callApi(url, (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
function callApi(url, callback) {
request
.get(url)
.then((response) => {
const json = JSON.parse(response);
callback(null, json);
})
.catch((err) => {
callback(err, null);
});
}
Some issues:
A missing return statement in getVehicleName
A syntax issue in getVehicleName[vehicles_URL[i]] (should be parentheses)
As the promises for getting the vehicle names are independent, you would not chain them, but use Promise.all
arrVehicleData will always only have one element. There is no reason for an array there where it is used.
You are also taking the wrong approach in using request.get. The bottom function turns that API from a Promise-API to a callback API, only to do the reverse (from callback to promise) in the function just above it. You should just skip the callback layer and stick to promises:
"use strict";
const request = require("request-promise");
const BASE_URL = "http://swapi.dev/api";
getJson(`${BASE_URL}/people/1`).then(data => {
return Promise.all(data.vehicles.map(getVehicleName));
}).then(vehicleNames => {
console.log(vehicleNames);
// Continue here...
});
function getVehicleName(url) {
return getJson(url).then(vehicle => vehicle.name);
}
function getJson(url, callback) {
return request.get(url).then(JSON.parse);
}
Finally, you should not use request-promise anymore since the request module, on which request-promise depends, has been deprecated
The getVehicleName doesn't return a promise. Instead it invokes a promise that by the time it will be resolved, the for loop invoking it will already be removed from the call stack.
This is a sample of promise chaining:
const promise = new Promise(resolve => resolve(1))
const promise1 = Promise.resolve(2)
const methodReturnPromise = () => new Promise(resolve => resolve(3))
promise.then(firstPromiseData => {
// do something with firstPromiseData
console.log(firstPromiseData)
return promise1
}).then(secondPromiseData => {
// do something with secondPromiseData
console.log(secondPromiseData)
return methodReturnPromise()
}).then(thirdPromiseData => {
// do something with thirdPromiseData
console.log(thirdPromiseData)
})
I am using node-serialport to communicate with a piece of hardware. It just writes a command and receives a response.
https://serialport.io/docs/en/api-parsers-overview
The following code works:
const port = new SerialPort(path);
const parser = port.pipe(new Readline({ delimiter: '\r', encoding: 'ascii' }));
const requestArray = [];
parser.on('data', (data) => {
// get first item in array
const request = requestArray[0];
// remove first item
requestArray.shift();
// resolve promise
request.promise.resolve(data);
});
export const getFirmwareVersion = async () => {
let resolvePromise;
let rejectPromise;
const promise = new Promise((resolve, reject) => {
resolvePromise = resolve;
rejectPromise = reject;
});
const title = 'getFirmwareVersion';
const cmd = 'V\r';
requestArray.push({
title,
cmd,
promise: {
resolve: resolvePromise,
reject: rejectPromise
}
});
await v2Port.write(cmd);
return promise;
};
Then from my app (which is written in electron/react) I can call the function:
<Button onClick={() => {
let data = await _api.getFirmwareVersion();
console.log('done waiting...');
console.log(data);
}>
Click Me
</Button>
Is there anyway I can refactor this code to make it more succinct?
Is there a way to get the Promise from the async function, rather than having to make a new Promise?
Is there a way to tap into the Transform Stream that already exists and pipe the Promise in there somehow?
I'm also new to async/await, and wanted to avoid using callbacks, especially in the React/Redux side of things.
I aim to have a lot of these endpoints for the api (i.e. getFirmwareVersion, getTemperature, etc...). So I want to make the code as concise as possible. I don't want the UI to have any underlying knowledge of how the API is getting the data. It just needs to request it like any other API and wait for a response.
Oh, I think I get it. The parser is receiving data constantly. So when a request comes, you wait for the next data and send it when it arrives. I suggest you to write an intermediate class.
Like this:
const SerialPort = require('serialport')
const Readline = require('#serialport/parser-readline')
const { EventEmitter } = require('events');
class SerialPortListener extends EventEmitter {
constructor(path) {
super();
this.serialPortPath = path;
}
init() {
this.serialPort = new SerialPort(this.serialPortPath);
const parser = this.serialPort.pipe(new Readline({ delimiter: '\r', encoding: 'ascii' }));
parser.on('data', data => this.emit('data', data));
}
}
Then you could modify the getFirmwareVersion like this:
const serialPortListener = new SerialPortListener(path);
serialPortListener.init();
export const getFirmwareVersion = () => {
return new Promise((resolve, reject) => {
serialPortListener.once('data', async (data) => {
try {
const cmd = 'V\r';
await v2Port.write(cmd);
resolve(data);
} catch (ex) {
reject(ex);
}
});
});
};
Based on help from Mehmet, here is what I ended up with:
const _port = new SerialPort(path);
const _parser = _port.pipe(new Readline({ delimiter: '\r', encoding: 'ascii' }));
const waitForData = async () => {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => reject('Write Timeout'), 500);
_parser.once('data', (data) => {
clearTimeout(timeoutId);
resolve(data);
});
});
};
const createAPIFunction = (cmdTemplate, validationString) => {
return async (config) => {
try {
// replace {key} in template with config[key] props
const cmd = cmdTemplate.replace(/{(\w+)}/g, (_, key) => {
return config[key];
});
_port.write(cmd + '\r');
const data = await waitForData();
// validate data
if (data.startsWith(validationString)) {
// is valid
return data;
} else {
// invalid data
throw new Error('Invalid Data Returned');
}
} catch (err) {
throw err;
}
};
};
export const getFirmwareVersion = createAPIFunction('V', 'V1');
export const enableSampling = createAPIFunction('G1{scope}', 'G11');
Below is simple example to demonstrate why I am trying to do using the fetch API. I am hoping that async fetchAsync() would block till it's returning data (or an exception) but the output shows that it doesn't.
constructor
entering fetchAsync...
** we are here!!! **
leaving fetchAsync.
finish initialization
I have been trying to figure out how to display the finish string (we are here) after finish initialization when my object is done initializing with the content of the file. Isn't await/async suppose to block till it's done?
class A {
filename = "./resources/test.json";
constructor() {
console.log("constructor");
this.fetchAsync(this.filename)
.then( data => this.initialize(data)
).catch(reason => console.log(reason.message))
}
async fetchAsync(filename) {
console.log("entering fetchAsync...");
let data = await (await fetch(filename)).json();
console.log("leaving fetchAsync.");
return data;
}
initialize() {
setTimeout(() => console.log("finish initialization"), 1000)
}
}
let a = new A();
console.log("*** we are here!!! ***");
await doesn't block - that would be very user and application unfriendly - it just waits for the promise to resolve before continuing, and can't be done on the top-level (yet). If you want we are here to display only after initialization is done, then you need to be able to access a promise that resolves once initialization is done, and then call then on it.
You should also ensure initalize returns a Promise so that it can be chained to the outer call on a.
So, you could try something like this:
const dummyRequest = () => new Promise(res => setTimeout(res, 1000, 'response'));
class A {
// filename = "./resources/test.json";
constructor() {
console.log("constructor");
}
startFetch() {
return this.fetchAsync(this.filename || 'foo')
.then(data => this.initialize(data)).catch(reason => console.log(reason.message))
}
async fetchAsync(filename) {
console.log("entering fetchAsync...");
// let data = await (await fetch(filename)).json();
const data = await dummyRequest();
console.log("leaving fetchAsync.");
return data;
}
initialize() {
return new Promise(resolve => {
setTimeout(() => {
console.log("finish initialization");
resolve();
}, 1000);
});
}
}
const a = new A();
a.startFetch()
.then(() => {
console.log("*** we are here!!! ***");
});
Constructors can't be async, which is why I made the startFetch function.
You must be forgetting what asynchronous means:
class A {
constructor() {
this.filename = "./resources/test.json";
console.log("constructor");
this.fetchAsync(this.filename)
.then( data => this.initialize(data)
).catch(reason => console.log(reason.message))
}
async fetchAsync(filename){
console.log("entering fetchAsync...");
let data = await fetch(filename);
console.log("leaving fetchAsync.");
return data.json;
}
initialize() {
setTimeout(() => {console.log("finish initialization"); console.log("*** we are here!!! ***"}, 1000)
}
}
let a = new A();
I'm creating a "class" that emits events such as error, data, downloadFile and initialize. Each event is fired after a request is made, and each event is fired by a method that has the same name:
class MyClass extends EventEmitter {
constructor(data) {
this.data = data
this.initialize()
.then(this.downloadFile)
.then(this.data)
.catch(this.error)
}
initialize() {
const req = superagent.post('url...')
superagent.send(data)
const res = await req // this will actually fire the request
this.emit('initialize')
this.url = res.body
return res
}
downloadFile() {
const req = superagent.put(this.url)
const res = await req; // this will actually fire the request
req.on('progress', (progress) => this.emit('downloadFile', progress)
//
// save to disk
//
return res
}
data() {
// Next in the sequence. And will fire the 'data' event: this.emit('data', data)
}
error(err) {
this.emit('error', err)
}
}
After that I have the data method to be called. My doubt is: Is there a design pattern to call the events in sequence without using Promises? Currently I'm using chaining, but I'm feeling that this isn't the best approach, maybe I'm wrong.
this.initialize()
.then(this.downloadFile)
.then(this.data)
.catch(this.error)
But I feel that could be a better approach.
Answers for bergi's questions:
a) Why are you using class syntax?
Because it's easier to inherit from EventEmitter and personally I think it's more readable than using a constructor
functin, e.g:
function Transformation(data) {
this.data = data
}
// Prototype stuffs here
b) How this code is going to be used
I'm creating a client to interact with my API. The ideia is that the user can see what is happening in the background. E.g:
const data = {
data: {},
format: 'xls',
saveTo: 'path/to/save/xls/file.xls'
}
const transformation = new Transformation(data)
// Events
transformation.on('initialize', () => {
// Here the user knows that the transformation already started
})
transformation.on('fileDownloaded', () => {
// Here the file has been downloaded to disk
})
transformation.on('data', (data) => {
// Here the user can see details of the transformation -
// name,
// id,
// size,
// the original object,
// etc
})
transformation.on('error', () => {
// Here is self explanatory, if something bad happens, this event will be fired
})
c) What it is supposed to do?
The user will be able to transform a object with data into a Excel.
It sounds like the transformation object you are creating is used by the caller solely for listening to the events. The user does not need a class instance with properties to get or methods to call. So don't make one. KISS (keep it super simple).
function transform(data) {
const out = new EventEmitter();
async function run() {
try {
const url = await initialise();
const data = await downloadFile(url);
out.emit('data', data);
} catch(err) {
out.emit('error', err);
}
}
async function initialise() {
const req = superagent.post('url...')
superagent.send(data)
const res = await req // this will actually fire the request
out.emit('initialize')
return res.body
}
async function downloadFile(url) {
const req = superagent.put(url)
req.on('progress', (progress) => out.emit('downloadFile', progress)
const res = await req; // this will actually fire the request
//
// save to disk
//
return data;
}
run();
return out;
}
It might be even simpler to leave out the (once-only?) data and error events and just to return a promise, alongside the event emitter for progress notification:
return {
promise: run(), // basically just `initialise().then(downloadFile)`
events: out
};
If you want another way to call the events in sequence, and if you're using a Node.js version that supports ES7, you can do the following :
class MyClass extends EventEmitter {
constructor(data) {
this.data = data;
this.launcher();
}
async launcher() {
try {
await this.initialize();
await this.downloadFile();
await this.data();
}
catch(err) {
this.error(err);
}
}
initialize() {
const req = superagent.post('url...');
superagent.send(data);
this.emit('initialize');
this.url = req.body;
return req;
}
downloadFile() {
const req = superagent.put(this.url);
req.on('progress', (progress) => this.emit('downloadFile', progress)
//
// save to disk
//
return req;
}
data() {
// Next in the sequence. And will fire the 'data' event: this.emit('data', data)
}
error(err) {
this.emit('error', err)
}
}
Explanation : instead of awaiting for your Promises inside your functions, just return the Promises and await for them at root level.