On my client app, I'm using Socket IO to check for unread events. I make a request to my backend, which sets a timeout of 5 seconds, then proceeds to check for unread events and sends any back.
// client
socket.on("response", ({ mostRecentMessages }) => {
// do some stuff first
socket.emit("listenForNew", { userId, currentMessagesFromEveryone });
})
// backend
socket.on("listenForNew", ({ userId, currentMessagesFromEveryone }) => {
if (currentMessagesFromEveryone && userId) {
const { MostRecentMessages } = require("./constants/models");
const filteredIds = [];
currentMessagesFromEveryone.forEach(message => {
filteredIds.push(message.conversation._id);
});
console.log("Entered!");
setTimeout(async () => {
const mostRecentMessages = await MostRecentMessages.find({
to: userId,
date: { $gt: connectedUsersAllMessages[userId].timeIn },
conversation: { $nin: filteredIds }
}).populate("to from conversation");
allMessagesSocket.sockets.connected[
connectedUsersAllMessages[userId].socketId
].emit("response", {
mostRecentMessages
});
}, 5000);
}
});
At first, it works fine. It prints Entered! one time for about 4, 5 requests. Then on the 6th, it starts to print Entered! twice.
Why is this happening and what am I doing wrong?
I'm in favor of the below approach:
Wait for X seconds (5 in our use case)
Call an async operation (execution time in unknown)
Wait until async execution complete
Wait for another X seconds before executing the next call
Implementation may be something like that:
const interval = 5000;
function next() {
setTimeout(async () => myAsyncOperation(), interval);
}
function myAsyncOperation() {
const mostRecentMessages = await MostRecentMessages.find({
to: userId,
date: { $gt: connectedUsersAllMessages[userId].timeIn },
conversation: { $nin: filteredIds }
}).populate("to from conversation");
allMessagesSocket.sockets.connected[
connectedUsersAllMessages[userId].socketId
].emit("response", () => {
mostRecentMessages();
next(); // "next" function call should be invoked only after "mostRecentMessages" execution is completed (or a race condition may be applied)
});
}
next();
I haven't compiled this code but I hope that the concept is clear
Related
I have a function handleSubmit that handles registering in Firebase in a react component. Inside, I want to handle errors with my setErrorTimeout function, which has a setTimeout that resets the error automatically after 3 seconds in this case..
The problem is, my Timeout is not executed, e.g the callback function inside the timeout is not being executed after 3 seconds, but everything else is.. why?
const handleSubmit = async e => {
e.preventDefault()
console.log(formDetails)
if (formDetails.password !== formDetails.passwordrepeat) {
setErrorTimeout(setRegisterError, {
message: 'Passwords do not match!',
})
return
}
console.log('Try')
console.log(formDetails.email, formDetails.password)
try {
auth.createUserWithEmailAndPassword(
formDetails.email,
formDetails.password
)
.then(userCredentials => {
if (userCredentials) {
const user = userCredentials.user
let success = user.sendEmailVerification()
console.log('success register:', success)
setRegisterSuccess(
'You registered successfully! please check your email!'
)
setFormDetails({})
}
})
.catch(error => {
console.log('ERROR!')
setErrorTimeout(error)
})
} catch (e) {
setErrorTimeout(e)
}
}
const setErrorTimeout = error => {
console.log('inside timeout!')
setRegisterError(error)
const timer = setTimeout(() => {
console.log('inside cb!')
setRegisterError(null)
}, 3000)
clearTimeout(timer)
console.log('after timeout!')
}
You're clearing the timeout right after you create it here:
const timer = setTimeout(() => {
console.log('inside cb!')
setRegisterError(null)
}, 3000)
clearTimeout(timer)
You probably want that clearTimeout call to be inside the callback, although it's not even strictly needed since the timeout already fired.
I want to make multiple requests with Axios without waiting it finish one on one.
what i want to do is, even when the first request is waiting, i want the second request is keep proccess and send the return, i dont want to waiting the first request because it will take long time..
async sendLog() {
setTimeout(() => {
if (this.proccessData === true) {
$nuxt.$axios.post('http://localhost/api/log/procreate', {logFile: this.form.logName})
.then((res) => {
this.sendLog();
})
}
}, 1000);
},
async addServer() {
$nuxt.$axios.post('http://localhost/api/servers/create', this.form)
.then((res) => {
this.proccessData = false;
}
}
But this code will run the next request when the current request is finished.
const foo = async() => {
const p1 = $nuxt.$axios.post();
const p2 = $nuxt.$axios.post();
const [res1, res2] = await Promise.all([p1, p2]);
// do with res1 and res2
}
see Promise.all
Not really sure if I understand your question correctly. If you want sendLog to be processed multiple times without waiting the previous one to finish, you might choose to use interval as following
async sendLog() {
setInterval(() => {
if (this.proccessData === true) {
$nuxt.$axios.post('http://localhost/api/log/procreate', {
logFile: this.form.logName
});
}
}, 1000);
}
UPDATE
Combining both solutions below, I wrote:
const startMusic = async () => {
let currentSong
let songPath
const songArray = [
{ path: require("../assets/sounds/Katsu.mp3"), song: mainTheme },
{ path: require("../assets/sounds/MainTheme2.mp3"), song: mainTheme2 },
{ path: require("../assets/sounds/MainTheme3.mp3"), song: mainTheme3 },
]
for (var i = 0; i < songArray.length; i++) {
currentSong = songArray[i].song
songPath = songArray[i].path
try {
await currentSong.loadAsync(songPath)
await currentSong.playAsync()
// setSoundObject(currentSong)
console.log("Music will start")
return new Promise(resolve => {
currentSong.setOnPlaybackStatusUpdate(playbackStatus => {
if (playbackStatus.didJustFinish) {
console.log("Just finished playing")
resolve()
}
})
})
} catch (error) {
console.log(`Error: ${error}`)
return
}
}
}
This actually plays the song, and the console logs occur on time ("Just finished playing" happens exactly when the song ends)
I'm trying to figure out how to play the next song.. and how will it know when it has reached the final song?
return new Promise(resolve => {
currentSong.setOnPlaybackStatusUpdate(playbackStatus => {
if (playbackStatus.didJustFinish) {
console.log("Just finished playing")
resolve()
}
})
}).then(() => console.log("Next song?"))
Figured how where to put the .then to get it to console log right after "Just finished playing" I'm just trying to see how to actually put the next song there
(then of course, telling it when to go back to the first song in the array)
Original Post
Working on an assignment for a react native app using expo-av library for Sound files.
Right now, the app has a startMusic function set in a Context file that is responsible for playing the app's background music. It only has one song for now:
const startMusic = async () => {
try {
await mainTheme.loadAsync(require("../assets/sounds/Katsu.mp3"))
await mainTheme.playAsync()
setSoundObject(mainTheme)
console.log("The first song is playing! Enjoy!")
} catch (error) {
console.log(`Couldnt load main theme: ${error}`)
return
}
}
It is used in the homescreen component's file like so:
const { startMusic } = useContext(MusicContext)
useEffect(() => {
startMusic()
}, [])
For the second song, I wrote another const in the MusicContext file:
const secondSong = async () => {
try {
await mainTheme2.loadAsync(require("../assets/sounds/MainTheme2.mp3"))
await mainTheme2.playAsync()
setSoundObject(mainTheme2)
console.log("Now playing the second track. Enjoy!")
} catch (error) {
console.log(`Could not play the second song: ${error}`)
return
}
}
Annnnnd… here is where my trouble lies. I know this wasn't gonna work but I wrote this in the component file to try to get the second song playing after the first song
useEffect(() => {
startMusic()
.then(secondSong())
}, [])
I know there's more to it than that but I'm having trouble.
Problem with your code is not just running one function after another (that would be as simple as startMusic().then(() => secondSong()) but still won't solve the problem), but the fact that your functions actually don't wait for a song to finish playing before resolving
You expect this line await mainTheme.playAsync() to pause function execution until the song has finished, but what it in fact does according to docs https://docs.expo.io/versions/latest/sdk/av/ is exactly only starting the playback (without waiting for it to finish)
With that being said, you need to determine the moment your playback finishes, then create a Promise that will only resolve after the playback is finished so that your second song can only start after the first
In the simplest form without error handling and such, it can look like this
const startAndWaitForCompletion = async () => {
try {
await mainTheme.loadAsync(require('../assets/sounds/Katsu.mp3'))
await mainTheme.playAsync()
console.log('will start playing soon')
return new Promise((resolve) => {
mainTheme.setOnPlaybackStatusUpdate(playbackStatus => {
if (playbackStatus.didJustFinish) {
console.log('finished playing')
resolve()
}
}
})
} catch (error) {
console.log('error', error)
}
}
the trick is of course the .setOnPlaybackStatusUpdate listener that will be called every so often with playback status, and by analyzing the status you can tell the song has finished playing. If you scroll to the bottom of the page I linked you will find other examples with status update
updated
const startAndWaitForCompletion = async (playbackObject, file) => {
try {
await playbackObject.loadAsync(file)
await playbackObject.playAsync()
console.log('will start playing soon')
return new Promise((resolve) => {
playbackObject.setOnPlaybackStatusUpdate(playbackStatus => {
if (playbackStatus.didJustFinish) {
console.log('finished playing')
resolve()
}
}
})
} catch (error) {
console.log('error', error)
}
}
////
const songs = [
{ path: require('../assets/sounds/Katsu.mp3'), song: mainTheme },
{ path: require('../assets/sounds/MainTheme2.mp3'), song: mainTheme2 },
{ path: require('../assets/sounds/MainTheme3.mp3'), song: mainTheme3 },
]
useEffect(() => {
(async () => {
for (let i = 0; i < songs.length; i++) {
await startAndWaitForCompletion(songs[i].song, songs[i].path)
}
})()
}, [])
I think you need to rethink this problem/solution to be more abstract.
Instead of making a new const and promise for every single song you want to play (which, as you said, isn't workable, and isn't scalable, like say if you wanted 10 songs instead of 2), make "startMusic" a function that plays an array of songs (each array index being a filepath to an MP3, like in your example), and
then resolve/reject the promise as needed.
A quick "startMusic" rewrite:
const startMusic(songArray) = async () => {
for (var songIndex in songArray) {
try {
await mainTheme.loadAsync(require(songArray[songIndex]))
await mainTheme.playAsync()
setSoundObject(mainTheme)
console.log("Song #", songIndex, "of ", songArray.length " is playing. Enjoy!")
} catch (error) {
console.log(`Couldnt load song: ${error}`)
return
}
}
}
A "Promise.all" chain could be useful here, too, if the above for-try-catch structure doesn't work: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Not familiar with the expo-av library, so there might be some specific quirks to look out for there, but I think re-writing "startMusic" to be an abstract function that plays an array of "N" songs is a much more optimal approach, and will minimize/eliminate your problems.
.then() accepts a function but you've provided the result of function execution by calling secondSong.
Do:
useEffect(() => {
startMusic()
.then(() => secondSong())
}, [])
Or just get rid of () after secondSong:
useEffect(() => {
startMusic()
.then(secondSong)
}, [])
Let's say I have a checkStatus() method which is triggered after a response to an endpoint is successful. Inside this there is a setInterval like so:
checkStatus() {
setInterval(() => {
client
.query({
query,
variables,
})
.then(res => {
if (res.status) {
console.log("FINISHED!");
}
});
}, 3000);
}
Basically I am querying an endpoint every 3 seconds. Once the res.status becomes true, I want to clear the interval. The component is still there and hasn't unmounted yet.
How do I achieve this?
Just clear interval in your check condition.
Example:
checkStatus() {
const interval = setInterval(() => {
client
.query({
query,
variables,
})
.then(res => {
if (res.status) {
console.log("FINISHED!");
clearInterval(interval)
}
});
}, 3000);
}
Basically you need to assign a value to the setInterval function and clear it into your callback:
checkStatus() {
const intervale = setInterval(() => {
client
.query({
query,
variables,
})
.then(res => {
if (res.status) {
console.log("FINISHED!");
clearInterval(intervale)
}
});
}, 3000);
}
You need to first store the reference returned by setInterval and pass it to clearInterval to clear it. For example, storing the reference in a variable ref -
checkStatus() {
const ref = setInterval(() => {
client
.query({
query,
variables,
})
.then(res => {
if (res.status) {
clearInterval(ref);
console.log("FINISHED!");
}
});
}, 3000);
}
Assign a variable to the interval and clear the interval based on the condition
this.interval = setInterval(() => {
client
.query({
query,
variables,
})
.then(res => {
if (res.status) {
console.log("FINISHED!");
clearInterval(this.interval)
}
});
}, 3000);
checkStatus() {
this.interval()
}
I'm having following code
if (input.isValidLink()) {
store()
.then(db => db.update(message.from, text, json))
.then(value => value.selectAllFromDB())
.then(users => makeRequest(users));
}
Amd makeRequest function
makeRequest(user) {
setInterval(() => {
users.forEach(user => {
httpRequest(user.json);
});
}, 10000);
}
What I'm trying to do, is selectAllFromDB function returns array of users from db, and it passed as argument to makeRequest function, which is looping thru each user, send request to receive json data and do it each 10 seconds, so it will not miss any changes. Each time when any user send the link, it should also start watching for this link. Problem is that on each received link it calls makeRequest function which creates another interval and if two links were received, I'm having two intervals. First looping thru this array
[{
id: 1234,
json: 'https://helloworld.com.json',
}]
And second thru this
[{
id: 1234,
json: 'https://helloworld.com.json',
}, {
id: 5678,
json: 'https://anotherlink.com.json',
}]
Is there any way this can be fixed so only one interval is going to be created?
You need to do something like this to make sure you only create one interval and are always using the latest users list:
let latestUsers;
let intervalId;
const makeRequest = (users) => {
latestUsers = users;
if (!intervalId) {
intervalId = setInterval(() => {
latestUsers.forEach(user => {
httpRequest(user.json);
});
}, 10000);
}
}
If you don't want variables floating around you can use a self-invoking function to keep things nicely packaged:
const makeRequest = (() => {
let latestUsers;
let intervalId;
return (users) => {
latestUsers = users;
if (!intervalId) {
intervalId = setInterval(() => {
latestUsers.forEach(user => {
httpRequest(user.json);
});
}, 10000);
}
}
})();