JS - Interrupt async execution - javascript

The actual Problem
The problem I am trying to solve is that a user may not provide enough information on a message (discord). To then get all of the necessary data the user is prompted to react to a bot message.
There are 3 stages of that bot message, "InitialPrompt", "TimeoutWarning" and "TimeoutSuccess(TicketFailure)".
What I wanted the solution to be
With the way I've written my code there is no way to abort the timeout after it has been initialized. I thought that throwing an error would stop the execution of a function. I guess that doesn't happen because async calls get queued up instead of being ran line by line.
Is there a way to do this without adding a boolean and checking infront of each function call?
The solution that I could come up with
const interPtr = {interrupted : false};
interruptSignal(interPtr);
if(interPtr.interrupted) return;
console.log("...");
...
The actual code
JS Fiddle
(async () => {
const sleep = async time => new Promise(resolve => setTimeout(resolve, time));
const interruptSignal = () => new Promise(async (res, rej) => {
console.log("interruptStarted")
await sleep(2000);
console.log("interruptRan");
throw "Interrupted"
});
const timeOutHandler = async () => {
interruptSignal();
console.log("TimeoutStarted")
await sleep(5000);
console.log("TimeoutWarning")
await sleep(5000);
console.log("TimeoutSuccess->TicketFailure")
};
try {
await timeOutHandler();
} catch (e) {
console.log(e);
}
})()

Related

Does NestJS guarantee to run async methods after return?

What will happen when a code without await an async function returns from a REST API of NestJS?
// modified from https://stackoverflow.com/questions/59829254
#Post()
async create(#Body() body: Dto, #Headers('id') id: string) {
body.id = id;
const item = await this.service.create(body);
this.service.anotherAsyncMethod().then(...).catch(...);
return item;
}
In this example, does the process always live to run the complete anotherAsyncMethod?
If it does, how can it be? Do NestJS or Javascript itself guarantee the way to execute?
It seems there are some waitings in simple test code.
async function asyncFunction() {
setTimeout(() => console.log("after 3s"), 3000);
}
asyncFunction();
console.log('END');
// output
// END
// after 3s
But I could not find the clue that it always work in NestJS scenario and the others.
In JavaScript, any piece of code that comes before the return statement will be executed. Do not forget that the await statement will only wait for the result, and if you put await before an async operation, it will wait for its result, but even if you did not put await, the async operation still placed in the event loop and will still be executed.
The only principle is that all these operations should be before the return statement. The return means termination of that function. Not only in JavaScript but in all languages, nothing will be executed after return.
function someOperations(status) {
return new Promise((res, rej) => {
setTimeout(() => {
console.log(status)
res('done');
}, 3000);
});
}
(async () => {
console.log('start...');
await someOperations('first');
someOperations('second');
console.log('second someOperations skipped for now');
return;
console.log('unreachable'); // will not be executed
})()

Await / Async with a conditional statement and IIAEF

Description
I want to conditionally call a db SELECT within a non-async function. I wrapped it in a IIAEF? like so
async function getScopedDataPolicies() {
const test = new Promise((res) => {
console.log('2')
res('test1')
})
return test;
}
function nonAsyncMethod() {
(async () => {
console.log('1')
const test = getScopedDataPolicies();
const policies = await test;
console.log('3')
})()
console.log('4')
}
nonAsyncMethod()
My expectation is
1
2
3
4
But I get
1
2
4
3
What wouldn't the await work in the immediately invoking method??
Look at these examples; am I missing something?
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/async_function
All await does is pause the execution of the current function, until an async task has finished, at which point execution will continue. It does not pause all of Javascript, that kind of behavior is synchronous programming, and Javascript is built to avoid it, because it makes for a really bad user experience if Javascript has to stall every time you make a rest request, etc.
update
Some possible workarounds:
await the IIFE, or take out the IIFE entirely. The IIFE is an async function, so it'll return a promise when it'scalled. You're not awaiting that promise, which is why your outer sync function continues on without waiting for it to finish.
async function nonAsyncMethod() {
await (async () => {
console.log('1')
const test = getScopedDataPolicies();
const policies = await test;
console.log('3')
})()
console.log('4')
}
Move console.log('4') into your IIFE. I know that console.log('4') might represent a whole log of code, but in some cases, you've just got to move it all inside - break up your function into smaller functions if you have to.
await inside the IIFE doesn't effect the outer function
To do so, you could either do this:
async function getScopedDataPolicies() {
const test = new Promise((res) => {
console.log('2')
res('test1')
})
return test;
}
async function nonAsyncMethod() {
await (async () => {
console.log('1')
const test = getScopedDataPolicies();
const policies = await test;
console.log('3')
})()
console.log('4')
}
nonAsyncMethod()
or this:
async function getScopedDataPolicies() {
const test = new Promise((res) => {
console.log('2')
res('test1')
})
return test;
}
function nonAsyncMethod() {
(async () => {
console.log('1')
const test = getScopedDataPolicies();
const policies = await test;
console.log('3')
})().then(() => {
console.log('4')
})
}
nonAsyncMethod()
Note: if you need to "wait" for nonAsyncMethod - you can only do await nonAsyncMethod(), or nonAsyncMethod.then(() => { continue here })

Node.js how to synchronously read lines from stream.Readable

I'm interacting with a child process through stdio, and I need to wait for a line from childProcess.stdout each time I write some command to childProcess.stdin.
It's easy to wrap an asynchronous method for writing like below:
async function write(data){
return new Promise(resolve=>{
childProcess.stdin.write(data,()=>resolve());
})
}
However, it turns out quite difficult when it comes to reading, since data from stdout must be processed using listeners. I've tried below:
const LineReader = require("readline")
const reader = LineReader.createInterface(childProcess.stdout);
async function read(){
return new Promise(resolve=>{
reader.once("line",line=>resolve(line));
})
}
But it always returns the first line.
I know I may achieve this using setInterval, And I've already implemented the functionality this way. But it obviously has an impact on the performance, so now I'm trying to optimize it by wrapping it into an asynchronous method.
Any suggestions and solutions will be appreciated!
Well, I ended up with something pretty similar to what you were trying. It makes some assumptions that are mentioned in the code and needs more complete error handling:
const cp = require('child_process');
const readline = require('readline');
const child = cp.spawn("node", ["./echo.js"]);
child.on('error', err => {
console.log(err);
}).on('exit', () => {
console.log("child exited");
});
const reader = readline.createInterface({ input: child.stdout });
// this will miss line events that occurred before this is called
// so this only really works if you know the output comes one line at a time
function nextLine() {
return new Promise(resolve => {
reader.once('line', resolve);
});
}
// this does not check for stdin that is full and wants us to wait
// for a drain event
function write(str) {
return new Promise(resolve => {
let ready = child.stdin.write(str, resolve);
if (!ready) {
console.log("stream isn't ready yet");
}
});
}
async function sendCmd(cmd) {
// get line reader event handler installed so there's no race condition
// on missing the return event
let p = nextLine();
// send the command
await write(cmd);
return p;
}
// send a sequence of commands and get their results
async function run() {
let result1 = await sendCmd("hi\n");
console.log(`Got '${result1}'`);
let result2 = await sendCmd("goodbye\n");
console.log(`Got '${result2}'`);
let result3 = await sendCmd("exit\n");
console.log(`Got '${result3}'`);
}
run().then(() => {
console.log("done");
}).catch(err => {
console.log(err);
});
And, for testing purposes, I ran it with this echo app:
process.stdin.on("data", data => {
let str = data.toString();
let ready = process.stdout.write("return: " + str, () => {
if (str.startsWith("exit")) {
process.exit();
}
});
if (!ready) {
console.log("echo wasn't ready");
}
});

socket.io and async events

I'm using socket.io and mongoose in my express server.
My socket is listening for events using the following code:
socket.on('do something', async () => {
try {
await doA();
doX();
await doB();
doY();
await doC();
} catch (error) {
console.log(error);
}
});
doA, doB and doC are async operations that writes on database using mongoose, but in general they can be any method returning a promise.
I want that 'do something' runs synchronously.
If the event queue processes more events at the same time I have consistency problems in my mongodb.
In other words if the server receives two 'do something' events, I want that the second event received is processed only when the first event is fully processed (after the await doC). Unfortunately the 'do something' callback is async.
How to handle this?
It's possible to implement a queue by adding the functions you want to run to an array, and then running them one by one. I've created an example below.
let queue = [];
let running = false;
const delay = (t, v) => {
return new Promise((resolve) => {
setTimeout(resolve.bind(null, "Returned value from Promise"), t)
});
}
const onSocketEvent = async () => {
console.log("Got event");
if (!running) {
console.log("Nothing in queue, fire right away");
return doStuff();
}
// There's something in the queue, so add it to it
console.log("Queuing item")
queue.push(doStuff);
}
const doStuff = async () => {
running = true;
const promiseResult = await delay(2000);
console.log(promiseResult);
if (queue.length > 0) {
console.log("There's more in the queue, run the next one now")
queue.shift()();
} else {
console.log("Queue empty!")
running = false;
}
}
onSocketEvent();
setTimeout(() => onSocketEvent(), 1000);
setTimeout(() => onSocketEvent(), 1500);
setTimeout(() => onSocketEvent(), 2000);
setTimeout(() => onSocketEvent(), 2500);
I would suggest adding a delay between each await. This will prevent deadlocks from occurring and fix your issue. For such things, I would suggest using the Caolan's async library.
Task delay example:
setTimeout(function() { your_function(); }, 5000); // 5 seconds
If your function has no parameters and no explicit receiver, you can call directly setTimeout(func, 5000)
Useful jQuery timers plugin

how to throw and catch error if function runs longer than n seconds

I have a function of the form:
async someFunction () {
try {
someStuff()
} catch (err) {
doSomeCleanup()
}
}
Which gets called multiple times with workers from a queue. Now I have a new requirement that a worker should fail if it takes longer than 30 seconds to run this function with a given worker. My gut instinct was to whip up something like:
const timeoutCheck;
const time = new Date();
async someFunction () {
try {
timeoutCheck = setInterval(() => {
if (new Date() - time > 30000) throw new Error("timeout");
}, 2000);
someStuff()
} catch (err) {
doSomeCleanup()
} finally {
clearInterval(timeoutCheck);
}
}
But as I quickly learned the setInterval gets run outside the try/catch block so the error isn't caught inside the catch block but in the main app and the whole app crashes rather than just a worker failing. Is there a good way to contain this error in the try/catch block or another way to accomplish erroring into the try/catch block if the function runs more than 30 seconds?
You can use Promise.race()
let stop = () => new Promise((_, reject) =>
setTimeout(reject, 2999, "rejected"));
let fn = () => new Promise((resolve, _) =>
setTimeout(resolve, 3000, "resolved"));
let someStuff = data => console.log(data);
let doSomeCleanup = err => console.error(err);
Promise.race([fn(), stop()])
.then(doSomeStuff, doSomeCleanup);

Categories

Resources