Wait, did I do this right?

Damien Cosset - Sep 23 '17 - - Dev Community

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
    })
Enter fullscreen mode Exit fullscreen mode

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
    }
})
Enter fullscreen mode Exit fullscreen mode

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
    }
})()
Enter fullscreen mode Exit fullscreen mode

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 :)

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .