Introduction
I had to solve an issue at work this week. In our meteor application, the goal was to make sure that certains actions were synchronous. If we had two tabs open, one would deposit money, then the other tab could be used to buy something. We needed to make sure that the second action could not happen before the first.
So, the goal was to build a synchronous queue in meteor. The queue had to be inside its own module and couldn't be called directly from the front-end ( so it had to be outside the method ). I REALLY struggled. It took me more than a day, but I learned a lot. I used promises to solve my problem. I am curious if another ( better? ) solution could have been used. There are most likely better ways to do this, so I would like your opinions.
The code
Sets the background
Cue epic music
Show a character's face, with a tear rolling down her cheek
Slowly turn the camera towards the code
Build up the music...
Front-end
//The method called by the front-end, the same one for all activities
Meteor.callPromise( 'handleActivity', data)
.then( () => {
// Do something on the front when action is handled on the back
}
.catch( err => {
// Do something on the front when an error occurs
})
Back-end
The meteor method handleActivity
Meteor.methods({
handleActivity( data ){
return new Promise( ( resolve, reject ) => {
SyncQueue.add( data )
.then( () => {
// back to front. Everything OK
resolve()
}
})
.catch( err => reject( err )) // Error, back to front
}
})
SyncQueue is the name of the module that holds the synchronous queue implementation.
SyncQueue
const SyncQueue = ( function(){
let isProcessing = false
let _actions = [] // stores actions's data
const add = data => {
return new Promise( ( resolve, reject ) => {
_actions.push([data, resolve, reject])
if( !isProcessing ){
isProcessing = true
return next()
}
})
}
const next = () => {
if( _actions.length > 0 ){
return processAction( _actions.shift() ) //Take the first action
} else {
isProcessing = false
return false // I don't know... just returning something I guess
}
}
const processAction = action =>Â {
const [ data, resolve, reject ] = action
const func = // a function ref I retrieve thanks to the activity type
func() //These functions also return promises
.then( () => {
resolve()
// this is the resolve from the add method
})
.catch(err => {
reject( err )
// this is the reject from the add method
})
.then( () => {
// Always runs yes ?
// Any more data to process ?
return next()
}
}
return {
// Only needs to show the add method
add
}
})()
Well, there you have it. I'm glad I solved it. I have no idea if this is elegant, or even following proper best practices. I also like that I didn't use callbacks... I wonder how I could have done it with async/await. I'm also aware that my lack of knowledge about Meteor was certainly a problem. I guess I could have used built-in functionalities to solve this?
Anyway, what do you think? Did I overcomplicate things? How would you have solved it? Would love to hear ideas :)