How can I promisify a websocket event based communication, to work like ajax?
constructor(){
this.ws = new WebSocket(...);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// send data to doRequest
};
},
async function doRequest(data){
this.ws.send(JSON.stringify(data));
return new Promise((resolve, reject) => {
// how to wait and get data?
resolve(data);
});
}
I was thinking to add my own event emmiter and fire event inside onmessage function, then hook the promise from doRequest to that event, but seems kind of convoluted?
Websockets can receive and send data in both directions at any time, whereas Promises only resolve once with one value. Thus Promises might not be the right tool here.
If you however want to write an abstraction around the response to one request, then a Promise makes perfectly sense:
Add an event listener that listens for the message event, but only listen for it once and remove it, send the data and resolve whenever the message event has been dispatched.
Remove function as this is a method of the class you are creating.
doRequest(data){
return new Promise(resolve => {
this.ws.addEventListener('message', event => {
resolve(event.data);
}, {once: true});
this.ws.send(JSON.stringify(data));
});
}
In your constructor only create the WebSocket instance.
constructor(){
this.ws = new WebSocket(...);
}
And then use it like this:
const socket = new YourWebSocketClass();
socket.doRequest({message: 'hello'}).then(message => {
const data = JSON.parse(message);
});
Related
I'm writing a test of event emitting in my Solana program as described here: https://github.com/coral-xyz/anchor/blob/master/tests/events/tests/events.js
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Events;
it("Is initialized!", async () => {
let listener = null;
let [event, slot] = await new Promise((resolve, _reject) => {
listener = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
await program.removeEventListener(listener);
assert.isAbove(slot, 0);
assert.strictEqual(event.label, "hello");
});
It works good if the instruction completes successfully. But if any error happens during execution, the test code silently waits forever for event emitting which expectedly doesn't happen.
Can anyone please suggest a way to deal with such exceptions so that they are not "swallowed" and thrown on upper level?
If I understand the issue correctly, you need to add some sort of timeout when waiting for the event, correct? In that case, you should be able to use a setTimeout to check the result of your listener and error if it hasn't fired, ie:
it("Is initialized!", async () => {
let listener = null;
let event = {label: ""};
let slot = 0;
setTimeout(function(){
assert.isAbove(slot, 0);
assert.strictEqual(event.label, "hello");
},5000);
[event, slot] = await new Promise((resolve, _reject) => {
listener = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
await program.removeEventListener(listener);
});
I have an app with two submit buttons. The first initiates an API call via a promise which takes a number of seconds. The second button gathers some user input then processes the output of the API request with their selection.
I'd like to start the request, have the user make their selection, then wait for the initial request to complete when they hit submit:
this.upload.addEventListener('change', (event) => {
....
// initial submit button
API.getHumanFaceLandmarks(data.corrected).then((landmarks) => {
console.log(landmarks);
this.humanFeatures = extractHumanFeatures.beginRender(
landmarks,
data.canvas,
this.EYE_MASK,
this.MOUTH_MASK,
this.EYE_SELECTOR,
this.MOUTH_SELECTOR);
}).catch(error => console.log('error', error));
});
pet.addEventListener('click', (event) => {
// second submit
renderPetswitch.outputFinalImage(
event.target.dataset.name,
this.humanFeatures, // <--- how do i wait for this value to be resolved before executing this function?
this.OUTPUT_CANVAS);
})
My API method looks like this:
getHumanFaceLandmarks: async function(data) {
console.log(data.substring(0, 50) + '...');
const headers = new Headers();
headers.append("Content-Type", "application/json");
const body = JSON.stringify({
"image": data
});
const requestOptions = {
method: 'POST',
headers: headers,
body: body,
redirect: 'follow'
};
const response = await fetch(constants.HUMAN_FACE_LANDMARKS_ENDPOINT, requestOptions);
return response.json();
}
How do I wait for an exisiting promise to complete when the user submits the second step of the process?
I'm trying to streamline the experience and thus would prefer not to do the request in one go.
You want to await two future events:
The resolution of getHumanFaceLandmarks()
The second submit
In such a situation Promise.all can help out, but for that to work, you need to first promisify the second event (the second submit).
To do that, you could promisify listining to a single event occurrence:
// Helper function: promisify event listening
const nextEvent = (elem, event) => new Promise(resolve =>
elem.addEventListener(event, resolve, { once: true })
);
Now you can do this:
(async function() {
const uploadEvent = await nextEvent(this.upload, 'change');
// ....
// initial submit button
const [landmarks, petEvent] = await Promise.all([
API.getHumanFaceLandmarks(data.corrected),
nextEvent(pet, 'click')
]);
// At this point we both have the landmarks and the second submit.
console.log(landmarks);
this.humanFeatures = extractHumanFeatures.beginRender(
landmarks,
data.canvas,
this.EYE_MASK,
this.MOUTH_MASK,
this.EYE_SELECTOR,
this.MOUTH_SELECTOR
);
renderPetswitch.outputFinalImage(
petEvent.target.dataset.name,
this.humanFeatures,
this.OUTPUT_CANVAS
);
})(); // immediately invoked.
You would need to have logic that prevents the user to do things in the wrong order. For instance:
The second submit should not be allowed when the upload had not been done yet.
Once the upload has been done, the user should not be allowed to do that again. The next action should be the second submit. Or if you would allow that, you need to include logic to cancel the previous upload processing...
pet.addEventListener('click', (event) => {
await.API.getHumanFaceLandmarks.then( //Rest of the function
renderPetswitch.outputFinalImage(
event.target.dataset.name,
this.humanFeatures, // <--- how do i wait for this value to be resolved before executing this function?
this.OUTPUT_CANVAS);
})
if you make this function async, you should be able to do something like await.
I have an open Websocket connection and it's handing out events. All good, but once a new event arrives, I need to do a whole lot of things and sometimes events arrive so quickly one after the other that there is no time to get the stuff done properly. I need some sort of queue inside this function that tells the events to take it easy and only keep going at most one per second, and otherwise wait in some sort of queue until the second elapses to go ahead and continue.
edit: No external libraries allowed, unfortunately.
ws = new WebSocket(`wss://hallo.com/ws/`);
ws.onmessage = readMessage;
async function readMessage(event) {
print(event)
//do important things
//but not too frequently!
}
How do I do that?
I found this but it goes over my simple head:
"You can have a queue-like promise that keeps on accumulating promises to make sure they run sequentially:
let cur = Promise.resolve();
function enqueue(f) {
cur = cur.then(f); }
function someAsyncWork() {
return new Promise(resolve => {
setTimeout(() => {
resolve('async work done');
}, 5);
}); } async function msg() {
const msg = await someAsyncWork();
console.log(msg); }
const main = async() => {
web3.eth.subscribe('pendingTransactions').on("data", function(tx) {
enqueue(async function() {
console.log('1st print: ',tx);
await msg();
console.log('2nd print: ',tx);
});
}) }
main();
"
I'd honestly use something like lodash's throttle to do this. The following snippet should solve your problem.
ws = new WebSocket(`wss://hallo.com/ws/`);
ws.onmessage = _.throttle(readMessage, 1000);
async function readMessage(event) {
print(event)
//do important things
//but not too frequently!
}
For achieving queuing, you can make use of "settimeout" in simple/core javascript.
Whenever you receive a message from websocket, put the message processing function in a settimeout, this will ensure that the message is processed not immediately as its received, but with a delay, hence in a way you can achieve queuing.
The problem with this is that it does not guarantee that the processing of messages is sequential as they are received if that is needed.
By default settimeout in javascript does give the guarantee of when the function inside will be triggered after the time given is elapsed.
Also it may not reduce the load on your message processor service for a high volume situation and since individual messages are queued two/more functions can become ready to be processed from setimeout within some time frame.
An ideal way to do so would be to create a queue. On a high level code flow this can be achieved as follows
var queue = [];
function getFromQueue() {
return queue.shift();
}
function insertQueue(msg) { //called whenever a new message arrives
queue.push(msg);
console.log("Queue state", queue);
}
// can be used if one does not want to wait for previous message processing to finish
// (function executorService(){
// setTimeout(async () => {
// const data = getFromQueue();
// await processData(data);
// executorService();
// }, 1000)
// })()
(function executorService(){
return new Promise((res, rej) => {
setTimeout(async () => {
const data = getFromQueue();
console.log("Started processing", data)
const resp = await processData(data); //waiting for async processing of message to finish
res(resp);
}, 2000)
}).then((data) =>{
console.log("Successfully processed event", data)
}).catch((err) => {
console.log(err)
}).finally(() => {
executorService();
})
})()
// to simulate async processing of messages
function processData(data){
return new Promise((res, rej) => {
setTimeout(async () => {
console.log("Finished processing", data)
res(data);
}, 4000)
})
}
// to simulate message received by web socket
var i = 0;
var insertRand = setInterval(function(){
insertQueue(i); // this must be called on when web socket message received
i+=1;
}, 1000)
I am trying to let a Web-Worker manage its state, meanwhile serving multiple async requests.
worker.ts file
let a =0; //this is my worker's state
let worker=self as unknown as Worker;
worker.onmessage =(e)=>{
console.log("Rec msg", e.data);
if(e.data === "+1"){
setTimeout(()=>{
a=a+1;
worker.postMessage(a);
},3000);
}else if(e.data=== "+2"){
setTimeout(()=>{
a=a+2;
worker.postMessage(a);
},1000)
}
}
And this is my main file: main.ts
let w =new Worker("./worker.ts", {type: "module"})
let wf =async (op: string)=>{
w.postMessage(op);
return new Promise<any>((res,rej)=>{
w.onmessage=res;
});
}
(async()=>{
let f1 = await wf("+1");
console.log("f1",f1.data);
})();
(async()=>{
let f2 = await wf("+2");
console.log("f2",f2.data);
})()
Only f2 is returned , and f1 is lost.
I have used timeouts to simulate say some async task done by worker themselves.
How do I receive both f1 and f2?
Your problem is that you are trying to take an event based API and use it as a Promise based one, but events may fire multiple times, while Promise should resolve only once.
The communication between the Worker and the main thread works by sending and receiving messages, but there is by default no one-to-one relation between these messages. Both ends of the communication (ports) will simply stack incoming messages, and handle them sequentially, when they'll get time.
In your code, the main thread's worker.onmessage handler of f1 has been overwritten by the second call f2 synchronously (one microtask later, but that's still synchronous for our matter).
You could attach your event using the addEventListener method, at least this way it wouldn't be overwritten. But even then, when the first message event will fire on worker, both handlers will think it's there own message that did arrive, while in fact it was the one of f2. so that's not what you need...
What you need is to set up a protocol of communication which would allow both ends to identify each task. You could for instance wrap all your tasks' data with an object containing a .UIID member, be sure both ends wraps their message this way, and then from main thread check that UUID to resolve the appropriate Promise.
But that can become a bit complicated to implement and to use.
My personal favorite way is to create a new MessageChannel per task. If you don't know this API, I invite you to read this answer of mine explaining the basics.
Since we are sure the only one message that will come through this MessageChannel is the response from the Worker to the one task we sent to it, we can await it just like a Promise.
All we have to do, is to make sure that in the Worker thread we respond through the transferred port instead of the global scope.
const url = getWorkerURL();
const worker = new Worker(url)
const workerFunc = (op) => {
// we create a new MessageChannel
const channel = new MessageChannel();
// we transfer one of its ports to the Worker thread
worker.postMessage(op, [channel.port1]);
return new Promise((res,rej) => {
// we listen for a message from the remaining port of our MessageChannel
channel.port2.onmessage = (evt) => res(evt.data);
});
}
(async () => {
const f1 = await workerFunc("+1");
console.log("f1", f1);
})();
(async () => {
const f2 = await workerFunc("+2");
console.log("f2", f2);
})()
// SO only
function getWorkerURL() {
const elem = document.querySelector( '[type="worker-script"]' );
const script = elem.textContent;
const blob = new Blob( [script], { type: "text/javascript" } );
return URL.createObjectURL( blob );
}
<script type="worker-script">
let a = 0;
const worker = self;
worker.onmessage = (evt) => {
const port = evt.ports[0]; // this is where we will respond
if (evt.data === "+1") {
setTimeout(() => {
a = a + 1;
// we respond through the 'port'
port.postMessage(a);
}, 3000);
}
else if (evt.data === "+2") {
setTimeout(() => {
a = a + 2;
// we respond through the 'port'
port.postMessage(a);
}, 1000)
}
};
</script>
I'm creating website using Johnny-five,React and node.js to control my Arduino board but I got stuck on handling async/await function. So, user is sending chosen port (COM1) for example to server, server then creates new instance of board
async function checkPortConnection(port) {
let board = new five.Board({port: port});
let success;
await board.on('error', () => {
success = false;
});
await board.on('ready', () => {
success = true;
});
return success;
}
I've thought that keyword await will stop function execution and wait for board response which takes about 7 seconds, but when I do this:
checkPortConnection(port).then((data)=>{
console.log(data);
});
I'm getting 'undefined', (because I'm getting success which is undefined?)
And after that server will send response if chosen port is correct or not.
But my question is, how to get proper response from checkPortConnection() function?
I think the issue is that you are listening for events, but this in and of itself isn't a Promise. Also, if they would be and you would use await you would never reach the code to register the ready event. The following should fix that issue:
async function checkPortConnection(port) {
return new Promise((resolve, reject) => {
let board = new five.Board({port: port});
board.on('error', error => resolve( false ));
board.on('ready', event => resolve( true ));
});
}
Personally I would also do the following, as the Promise will either use then or catch later, so you might want to ignore the boolean bit altogether:
board.on('error', reject);
board.on('ready', resolve);