In node i keep checking if connection is available if it is it will get token and store in variable but if not then catch error will be thrown and it will then execute setTimeout to try again in 2 seconds.
But i think i need a clean way to clear the setTimeout because when i log it i keep getting higher number in Symbol(asyncId) this to me feels like it keeps adding setTimeout more and more to memory?
export const getToken = async () => {
try {
if (!GLOBAL_VARS.authToken) {
await auth(`Getting token`)
}
// Do more stuff
} catch (error: any) {
tokenTimeout()
}
}
getToken()
export const tokenTimeout = () => {
setTimeout(() => {
getToken()
}, 2000)
}
Yest it will add more setTimeouts to the memory.
you can clear it like this:
export const tokenTimeout = setTimeout(() => {
getToken()
}, 2000)
export const getToken = async () => {
try {
if (!GLOBAL_VARS.authToken) {
await auth(`Getting token`)
}
// Do more stuff
} catch (error: any) {
const timeout = tokenTimeout()
}
}
getToken()
/// use it wherever you want
clearTimeout(timeout);
Memory is going to increase because of whatever you are doing with auth() and not because you are creating timeouts.
The issue I see with your code is not really using async await properly. It should look more like
const sleep = (ms) =>
new Promise((resolve) => {
setTimeout(resolve, ms);
});
export const getToken = async (count = 0) => {
try {
if (!GLOBAL_VARS.authToken) {
await auth(`Getting token`);
}
} catch (error: any) {
await sleep(2000);
if (count < 10) await getToken(++count);
else throw new Error('Taking too long to get auth token');
}
}
(async function () {
await getToken();
})();
Related
I'm working on an audio recording class that either runs for an allotted period of time (such as 5 seconds) or can be stopped early by the user.
I'm using setTimeout to define the recording length, which works. However, I'm having trouble getting setTimeout working with a "stop" button. The error is as follows:
Cannot read properties of null (reading 'stop')
When the startRecording function executes, the handleStopRecording function is called which sets a timer with the "stopRecording" function. If the "stopRecording" function is called before the time elapses (by pressing the "stop" button), the function call that was initially in setTimeout will still execute when the timer expires, causing an error.
I tried fixing this by using clearTimeout, but then the "context" of the original function call is lost and we get the same error:
Cannot read properties of null (reading 'stop')
Unless I'm mistaken, I think this is an issue with closure of the setTimeout function - however I'm not sure how to clear the function early with a stop button and limit recording time.
Thank you in advance!
App.js (React.js)
import AudioRecorder from "./audioRecorder";
const App = () => {
const [recordedNameClipURL, setRecordedNameClipURL] = useState(null);
const [timeoutId, setTimeoutId] = useState(null);
const recorder = new AudioRecorder();
const startRecording = () => {
recorder.start();
handleStopRecording();
};
const handleStopRecording = async () => {
const id = setTimeout(stopRecording, 3000);
setTimeoutId(id);
};
const stopRecording = async () => {
clearTimeout(timeoutId);
const response = await recorder.stop();
setRecordedNameClipURL(response);
};
return (
...
);
};
audioRecorder.js
class AudioRecorder {
constructor() {
this.audioRecorder = null;
this.audioChunks = [];
}
initialize = async () => {
try {
await this.isSupported();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.audioRecorder = new MediaRecorder(stream);
this.audioRecorder.addEventListener("dataavailable", event => {
this.audioChunks.push(event.data);
});
} catch (err) {
console.log(err.message);
}
};
start = async () => {
try {
await this.initialize();
this.audioRecorder.start();
} catch (err) {
console.log(err.message);
}
};
stop = async () => {
try {
this.audioRecorder.stop();
const blob = await this.stopStream();
return URL.createObjectURL(blob);
} catch (err) {
console.log(err.message);
}
};
stopStream = () => {
return new Promise(resolve => {
this.audioRecorder.addEventListener("stop", () => {
const audioBlob = new Blob(this.audioChunks, {
type: this.audioRecorder.mimeType,
});
resolve(audioBlob);
});
});
};
isSupported = () => {
return new Promise((resolve, reject) => {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
resolve(true);
}
reject(new Error("getUserMedia not supported on this browser!"));
});
};
}
export default AudioRecorder;
Store the timer inside a React Ref instead
I usually store timeout/interval IDs in a React ref, because storing the handle isn't really "application state" the way that other things are. Sometimes it's needed to avoid render thrashing.
Here's what that looks like:
let timerRef = React.useRef(null)
const handleStopRecording = async () => {
timerRef.current = setTimeout(stopRecording, 3000)
}
const stopRecording = async () => {
clearTimeout(timerRef.current)
timerRef.current = null // good idea to clean up your data
const response = await recorder.stop()
setRecordedNameClipURL(response)
}
Code that needs to know if the timer is running should consult the timerRef; no additional state is needed:
let timerIsRunning = !!timerRef.current
You can try using a boolean value to check if the process is stopped. You can store it in state and change its value when starting or stopping
const [isStopped, setIsStopped] = useState(false);
const handleStopRecording = async () => {
const id = setTimeout(() => {
if(!isStopped){
stopRecording
}
}, 3000);
setTimeoutId(id);
};
This question already has answers here:
Timeout in async/await
(3 answers)
Closed 1 year ago.
In my express application, I am making call to 2 APIs. The 2nd API is managed by 3rd party and sometimes can take more than 5 seconds to respond. Hence, I want to just wait for 1 second for the API to respond. If it does not, just proceed with data from 1st API.
Below is the mock-up of the functions being called.
I am thinking to use setTimeout to throw error if the API takes more than 1 second. If the API responds within 1 second then I just cancel the setTimeout and no error is ever thrown.
But there is problem with this approach:
setTimeout errors cannot be catched using try...catch block.
I cannot use axios's timeout option, as I still need to wait for the 2nd API to finish the processing and save the data in the DB. This will ofcourse, can happen later, when the 2nd API call finishes.
// Function to simulate it's taking time.
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Track whether it took time.
let isTimeOut = false
async function test() {
console.log('starting')
try {
const one = await apiCall1()
const myt = setTimeout(() => {
console.log('Its taking time, skip the 2nd API Call')
isTimeOut = true
throw new Error('Its taking time')
})
const two = await apiCall2(myt)
} catch (error) {
console.log(error)
}
saveInDB({ ...one, ...two })
}
async function apiCall2(timeOutInstance) {
console.log('start-apiCall')
await cWait(1800)
clearTimeout(timeOutInstance)
if (isTimeOut) saveInDB()
console.log('done-apiCall')
}
async function apiCall1() {
await cWait(5)
}
async function saveInDB(data) {
console.log('saveInDB')
}
test()
please note, this is not the answer as it was when it was accepted
as I misread the question and failed to call saveInDB in a timed out
situation
Promise.race seems perfect for the job
Also, you'd actually use your cWait function, not for mock-up, but to actually do something useful ... win the race :p
const api2delay = 800;
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const TIMEDOUT = Symbol('TIMEDOUT');
async function cReject(ms) {
return new Promise((_, reject) => setTimeout(reject, ms, TIMEDOUT));
}
function apiCall2timeout(timeoutCallback) {
const resultApi2 = apiCall2();
const timeout = cReject(1000);
return Promise.race([resultApi2, timeout])
.catch(e => {
if (e === TIMEDOUT) {
resultApi2.then(timeoutCallback);
} else {
throw e;
}
});
}
async function test() {
console.log('starting')
let one, two;
try {
one = await apiCall1();
two = await apiCall2timeout(saveInDB);
} catch (error) {
console.log('error', error)
}
saveInDB({
...one,
...two
})
}
async function apiCall2() {
console.log('start-apiCall2')
await cWait(api2delay)
console.log('done-apiCall2')
return {
api2: 'done'
}
}
async function apiCall1() {
await cWait(5)
return {
api1: 'done'
}
}
async function saveInDB(data) {
console.log('saveInDB', data)
}
test()
Note: I changed where one and two were declared since const is block scoped
I you run with await cWait(800) in apiCall2, the saveInDB will run with both data.
But if you run await cWait(1800), the saveInDB will run 2 times.
// Function to simulate it's taking time.
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// https://italonascimento.github.io/applying-a-timeout-to-your-promises/
const promiseTimeout = function (ms, promise) {
// Create a promise that rejects in <ms> milliseconds
let timeout = new Promise((resolve, reject) => {
let id = setTimeout(() => {
clearTimeout(id);
reject('Timed out in ' + ms + 'ms.')
}, ms)
})
// Returns a race between our timeout and the passed in promise
return Promise.race([
promise,
timeout
])
}
// Track whether it took time.
let isTimeOut = false
async function test() {
console.log('starting')
const one = await apiCall1() // get data from 1st API
let two = {};
try {
two = await promiseTimeout(1000, apiCall2())
} catch (error) {
isTimeOut = true;
console.log(error)
}
saveInDB({ ...one, ...two })
}
async function apiCall2() {
console.log('start-apiCall')
await cWait(800)
console.log('done-apiCall', isTimeOut)
if (isTimeOut) {
saveInDB({ 2: 'two' })
}
return { 2: 'two' }
}
async function apiCall1() {
await cWait(5)
return { 1: 'one' }
}
async function saveInDB(data) {
console.log('saveInDB', data)
}
test()
I have a main function/file which calls a doLogic function/file. This doLogic opens up a database connection, and I am trying to use process.on('SIGINT') to cleanup the database, however, when I hit CTRL-C the process doesn't wait for the asynchronous calls.
main.js
import {doLogic} from "./doLogic"
import {cleanup} from "./cleanup"
// Attach SIGINT listener
process.on("SIGINT", async () => {
await cleanup()
process.exit(0)
})
async function run() {
doLogic()
}
run()
doLogic.js
import { connectionIds } from "./cleanup"
export async function doLogic {
for (const desiredConnection of desiredConnections) {
let id = await connectToDB(desiredConnection)
connectionIds.add(id)
}
}
cleanup.js
export const connectionIds = new Map()
// This function is just to simulate disconnecting
unlockConnection(connectionId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("resolve");
}, 5* 1000);
setTimeout(() => {
reject("reject");
}, 5 * 1000);
});
}
export async function cleanup() {
let asyncCalls = []
for(const lockedConnection of connectionIds.keys()) {
// I have also tried below with await unlockConn
// But not much difference
asyncCalls.push(unlockConnection(lockedConnection))
}
return Promise.all(asyncCalls)
}
The weird thing is that it is strictly doLogic() which seems to prevent the await cleanup() call from actually awaiting. If I comment this out and just pause the program with stdin() then if I send a SIGINT await cleanup() seems to work fine. Also I can confirm that there are actually connections in the map.
Hi I am write a unit test case of this function. When I run this function from the unit test case then it covers all statements but setInterval complete lines are not covered.
Does anyone know how to cover it in javascript? I am using mocha.
const open = async function (config) {
...... set of lines..
let retryIn = setInterval(async () => {
try {
client = await connect(config);
clearInterval(retryIn);
return client;
} catch (err) {
//error
}
}, 20000);
};
I am simply calling it like this
it("###test", async () => {
open(config);
});
});
First of all, you should never use setInterval in the case where you want to retry a task that has failed. You should use setTimeout instead.
Besides that, you cannot return a value from a classical callback base function like setInterval or setTimeout. So in its current form, the promise returned when calling open will be resolved before any connection is made.
With await/async you can create a clean and simple setup for such a situation:
function wait(seconds) {
return new Promise((resolve, _) => setTimeout(resolve, seconds))
}
async function connect() {
throw new Error('failed')
}
async function open(config) {
let client;
while (client === undefined /* || retries > maxRetries*/ ) {
try {
client = await connect(config);
} catch (err) {
// if connection failed due to an error, wait n seconds and retry
console.log('failed wait 2 seconds for reconnect');
await wait(2000)
// ++retries
}
}
if (!client) {
throw new Error('connection failed due to max number of retries reached.')
}
return client
}
async function main() {
let connection = await open()
}
main().catch(err => console.log(err))
You can further extend this snippet by adding a retry limit. See the comments for a rough idea on how that can be achieved.
To test the above code, you would write:
it("###test", function() {
return open(config);
});
Someone posted an answer about fake timers and then deleted it , The answer was correct so I re-posted again.
You can use sinonjs to create fake timers
Fake timers are synchronous implementations of setTimeout and friends
that Sinon.JS can overwrite the global functions with to allow you to
more easily test code using them
But from your code, it seems you are trying to test async code, in mocha, this can be achieved like this
describe('somthing', () => {
it('the result is 2', async () => {
const x = await add(1, 1)
expect(x).to.equal(4);
});
});
With something closer to your code
async function open() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('done')
}, 1000);
});
};
describe('somthing', () => {
it('###test', async () => {
const x = await open()
chai.expect(x).to.equal("done");
});
});
Just wrap to Promise
const open = async function (config) {
...... set of lines..
return new Promise((resolve, reject) => {
let retryIn = setInterval(async () => {
client = await connect(asConfigParam);
clearInterval(retryIn);
return client;
}, 20000);
return resolve(retryIn);
});
};
it("###test", async () => {
const result = await open(config);
console.log('result', result)
});
Im trying to use a while loop with my util() function (its commented out at the bottom of the code). When I try to run the program, I am stuck in an endless loop where i dont get farther than where I'm console logging out "getProjects running"
const axios = require("axios");
const _ = require("lodash");
axios.defaults.headers.common["Private-Token"] = "iTookMyPrivateKeyOut";
const user = "yshuman1";
let projectArray = [];
let reposExist = true;
async function getProjects() {
console.log("getProjects running");
await axios
.get(`https://gitlab.com/api/v4/users/${user}/projects`)
.then(function(response) {
const arr = _.map(response.data, "id").forEach(repo => {
projectArray.push(repo);
});
console.log(projectArray);
});
}
function deleteRepo(projectArray) {
console.log("array size", projectArray.length);
const arr = _.map(projectArray).forEach(item => {
axios
.delete(`https://gitlab.com/api/v4/projects/${item}`)
.then(() => {
console.log("deleted project ID: ", item);
})
.catch(error => {
console.log(error);
});
});
}
function util() {
getProjects()
.then(() => {
if (projectArray.length == 0) {
reposExist = false;
}
if (projectArray.length < 20) {
console.log("array is less than 20");
reposExist = false;
}
deleteRepo(projectArray);
})
.catch(error => {
console.log(error);
});
}
// while (reposExist) {
// util();
// }
The while loop is synchronous, while everything in any .then (or promise await) will be asynchronous. The initial thread will never terminate. Your code will simply queue up unlimited calls of getProjects which will only console.log.
The simple solution would be to figure out how often you want to call util (once a second? once every 5 seconds?) and await a Promise that resolves after that amount of time on each iteration.
let reposExist = true;
function util() {
console.log('running');
}
const resolveAfter5 = () => new Promise(res => setTimeout(res, 5000));
(async () => {
while (reposExist) {
util();
await resolveAfter5();
}
})();