socket.io control execution order with nested async and await - javascript

In my app, I have multiple users submitting data "at once" via a socket.event.
Because I need to know if every user has submitted their data successfully, I try to indentify the first and last entry.
For that, I call an async function on the event, which itself includes "await".
Basically, the function looks for a value in the database and then does something depending on whether it exists or not.
Because of that, I need the function block (with its awaits) to be fully executed before the next instance of the function is called.
However, what I found is that the as soon as I call await inside the function, the next instance of said function starts executing, which obviously messes everything up.
socket.on("entries", async entries => {
const firstEntry = await match.noEntries(mid);
if (firstEntry) {
//do sth
}
await match.submitEntries(mid, socket.pid, entries);
});
//FUNCTION
noEntries: async (mid) => {
const entries = await database.query("...");
return (entries.length == 0);
}
Is there any way I can prevent the next instance of the function to be executed before it's predecessor has finished?
Thanks in advance!

Related

Using JavaScript Promise All - is this syntax valid?

I have two tables with users, where each id for one user is same in both tables (don't ask why I have two user tables).
At some point, I need to filter users from table 1, and if certain condition is true, I store a promise (deleting request) for each user into (let's call it) tableOnePromises. I do the same for table 2.
In order to empty table 2, I MUST first empty table one due to some requirements.
this is what I did:
let tableOnePromises = [];
let tableTwoPromises = [];
tableOne.forEach(item => {
if(item.deactivated) {
const tableOneDeleted = supabase
.from("table-one")
.delete()
.match({id: item.id});
tableOnePromises.push(tableOneDeleted);
const tableTwoDeleted = supabase
.from("table-two")
.delete()
.match({id: item.id});
tableOnePromises.push(tableTwoDeleted);
}
});
await Promise.all(tableOnePromises).then(() => {
return Promise.all(tableTwoPromises)
}).catch(err => console.log(err));
Assuming the code using await is inside an async function (or at the top level of a module), the syntax is correct, but it's probably not what I'd use (in general, avoid mixing async/await with explicit callbacks via .then and .catch), and separately it's probably not working quite as you expect (this is borne out by your saying that your code was failing to delete from table-two).
For any particular id value, your code starts deleting from table-one and then immediately starts deleting from table-two without waiting for the deletion in table-one to complete:
// STARTS the deletion but doesn't wait for it to finish
const tableOneDeleted = supabase
.from("table-one")
.delete()
.match({id: item.id});
// ...
// Starts deleting from `table-two`, even though the item may still be in `table-one`
const tableTwoDeleted = supabase
.from("table-two")
.delete()
.match({id: item.id});
Remember that a promise is just a way of observing an asynchronous process; by the time you have the promise, the process it's observing is already underway.¹ So even though you don't wait for the table-two promises until later, you start the table-two deletions immediately.
...I MUST first empty table one due to some requirements...
If by "empty" you mean just that you have to ensure you've done the delete for a particular id on table-one before doing it on table-two, you need to wait for the table-one deletion to be completed before starting the table-two deletion. I'd put that in a function:
async function deleteItem(id) {
await supabase
.from("table-one")
.delete()
.match({id});
await supabase
.from("table-two")
.delete()
.match({id});
}
Then the code becomes:
const promises = [];
for (const {deactivated, id} of tableOne) {
if (deactivated) {
promises.push(deleteItem(id));
}
}
await Promise.all(promises); // With the `try`/`catch` if desired
...or if it's okay to make two passes through the array:
await Promise.all( // With the `try`/`catch` if desired
tableOne.filter(({deactivated}) => deactivated)
.map(({id}) => deleteItem(id))
);
¹ "...by the time you have the promise, the process it's observing is already underway." That's the normal case. There is unfortunately a popular document DB library that doesn't start its work on something until/unless you call then on the promise for it. But that's an exception, and an anti-pattern.

Repeatedly call a function that returns a promise until one of them contains a specific response

Hello I need to call a REST function with an ID, that returns a promise in React.js. This function will at some point contain a certain value in its response when called . Until another service has processed an initial request this value will be null.
This is what I have done so far:
while(myVariable){
myfunction(myID).then( (response) => {
if(response['value'] != null
myVariable = false;
}
});
}
The problem with this code is that the while loop is called as fast as possible and thus completely utilises the computer. So I need a function that allows me to poll for a result by an ID until the response of one of the function calls contains a valid response.
I have tried the following method but without success, because I don't have a fixed number of runs:
Wait promise inside for loop
Thanks in regards.
As you state, the problem is that the while loop runs eagerly, not waiting for each promise to resolve.
One way to solve that is to use recursion. Recursion gives you more control over when exactly you want to 'loop' next:
let getValue = () => {
myFunction(myID).then(response => {
if (response['value'] === null) {
setTimeout(getValue);
} else {
// here you know the other service has processed the initial request
}
});
};
First I wrapped the whole thing in a function called getValue. Note that this function is only called again after the promise resolves. (The call to setTimeout is a trick to use recursion without consuming the stack.) If this still runs too quickly, pass an additional parameter of 100 or so to the setTimeout invocation.
Alternatively, you may be able to use async/await similarly to the link you shared. I'm no expert on async/await but it should work the same with while loops as with for loops, judging by this and this.
You can use the async function with await.
I also use a delay function to delay each call to the myfunction().
While you get a response, you can break the while loop.
const delay = ms => new Promise((resolve, reject) => setTimeout(resolve, ms));
async function main() {
const myID = 1;
let response;
while (true) {
response = await myfunction(myID);
if (response["value"] != null) {
break;
}
await delay(5000);
}
//do Something once you get the response here below:
}
main();

How to return a promise resolved from a callback

I have an issue with my promise logic (still pretty new to JS), and I cannot find what is wrong.
Looking at how hard it is for me to do what I want, I can only guess that's it's not the correct way to do it. I did found another way to fix it, but I am still interested if there is a way to do what I want or not.
Here are some details:
I have a MessageQueue.sendMessage() which adds the message I give to the PendingMessageCache. When it can, the MessageQueue takes the cache and sends the Message to the server.
Sometimes I need to do some stuff after the message is sent. So I added a callback, saved and invoked by the MessageQueue when it sends the message. It's working great. The call looks basically like this:
await MessageQueue.sendMessage(myMsg, () => {markMessageAsSent()});
For one new type of message, I need to wait for the message being effectively sent because the app is completely cleaned after it.
As the sendMessage just adds the message to the cache and returns, the callback is later called but in this case it's too late, the app was restarted.
The message is basically a lastMessage sent to the server to say that this user removed the specific application instance.
So I think that what I want would be something like this:
deleteAccount = async () => {
const aMagicalCallback;
// this call adds the message to the cache,
// and ask the MessageQueue to call aMagicalCallback when that lastMsg is sent
await MessageQueue.sendLastMessage(lastMsg, <aMagicalCallback>);
// return the callback which would be a promise.
// this promise is resolved when it is resolved
// when the MessageQueue invokes the callback <aMagicalCallback>
return <aMagicalCallback>;
}
what I would like this to do is basically
// wait for the lastMessageBeingEffectively sent
await deleteAccount();
app.eraseEverything();
app.restart();
Let me know if that is not clear at all.
And thanks
You should return a Promise instead of using async. So you can choose when to resolve the promise, even in a nested callback function:
deleteAccount = () => new Promise(resolve => {
MessageQueue.sendLastMessage(lastMsg, () => {
// do your thing
resolve();
});
});
Then you can await it just like a regular async function
// this awaits until the resolve() above has been called
await deleteAccount();
app.eraseEverything();
app.restart();
As I understand it, you're already adding a callback function to every message. Instead of thinking of a separate callback to be injected after the promise is created, you could have the original callback check for the size of the message queue and execute
app.eraseEverything();
app.restart();
when it's empty.

Wait for function to execute before unloading javascript

I have a piece of code which i wish to run before the browser refreshes or redirects
window.addEventListener("beforeunload", async (event) => {
await this.save()
});
the method i'm running involves parsing JSON , updating it , and saving it back to the sessionStorage.Is there a way to make sure this.save() completes before unloading? or at least have it run in the background while the page redirects.
I have tried removing the async and await code, but does not work.
EDIT here is the save method:
saveCanvas = async (call_back_) => {
const FlowState = JSON.parse(sessionStorage.getItem('FLOWSTATE'))
...
...
await html2canvas(div).then((screenshot) => {
...
...
sessionStorage.setItem('FLOWSTATE', JSON.stringify(FlowState))
})
}
}
beforeload is the final event before leaving the page(reload leave and reenter for javascript runtime). if you use async functions(include async-await, promise, settimeout) in the event listening functions, it can't be executed anymore.
I think there may be async executions in save function, so it won't work even if you remove async-await keyword.
EDITED
Now, I actually suggest you change the layout.. the logic is to have a while loop awaiting saveCanvas(because while behaves well with await). So when the page is unloading, the last html2canvas(div) would be saved
saveCanvas = async(call_back_) => {
const FlowState = JSON.parse(sessionStorage.getItem('FLOWSTATE'))
await html2canvas(div)
sessionStorage.setItem('FLOWSTATE', JSON.stringify(FlowState))
}
let toRun=1
while(i){await saveCanvas()}
window.addEventListener("beforeunload", (event) => { toRun=0 });

Sequential async operations for each in Node JS

I am working on a database migration. This requires querying one DB, getting an array of records and perform a set of async operations to insert the data in the new DB. In order to keep data consistency, I want to insert the records one at the time so I want each operation to run sequentially. The only way I have found to do these is using recursion.
Is there a cleaner way of doing this same thing? I know there is a library called async https://caolan.github.io/async/v3/ which I never tried before.
The recursive method I have written looks like this:
const insertItem = async (data) => {
let item = data[0];
if (!item) {
//I am done return
return;
}
try {
//Do multiple await calls to insert record into new database
} catch (e) {
//Recover from error (DB rollbacks, etc)
} finally {
//Remove inserted or failed item from collection.
data.shift();
await insertItem(data);
}
};
//Query original database
getInfo().then((data) => insertItem(data));
You can use sync loop for...of it will wait for HTTP response.
const dataArr = ['data1', 'data2', 'data3'];
async function processItems(arr){
for(const el of arr) {
const response = await insertData(el);
// add some code here to process the response.
}
};
processItems(dataArr);
The posted code achieves iterating through the data collection (an array) retrieved from the first data base by using data.shift to mutate the argument array before calling the single function handling everything recursively.
To clean this up, remove the shift and recursive calls by splitting the data processing function into two:
one function to step through data retrieved from the first data base,
a second function to insert records in the second data base as required.
This removes the need for the .finally clause and leaves the structure of the code looking more like
async function insertData(data) {
for( let index = 0 ; index < data.length; ++index) {
await insertItem( data[index]);
}
}
async function insertItem( data) {
try {
//Do multiple await calls to insert record into new database
} catch (e) {
//Recover from error (DB rollbacks, etc)
// throwing an error here aborts the caller, insertData()
}
}
getInfo().then( insertData).catch( /*... handle fatal error ...*/);
Depending on preferred style, insertItem could be declared as nested function within insertData to keep it looking neat, and insertData could be written as an anonymous function argument of the then call after getInfo().
It is possible, of course, to perform asynchronous operations sequentially be other means, but using await inside an async function is perhaps the simplest coding method.

Categories