DEV Community

Cover image for Event tracking with Mixpanel and NodeJs, The Callback in Async scope magic!
mohieddin
mohieddin

Posted on • Updated on

Event tracking with Mixpanel and NodeJs, The Callback in Async scope magic!

Hello World!

It's been a while (a long long time ago πŸ™„ ) since my last post, long story short there is no specific reason, just me lazy to post.

Note:

I'm not going through what is Mixpanel and why we decided to use it at Supahands, the focus here is on a challenge we faced and how we tackled it

Use case:

We've been working on integrating an event tracking service to one of our backend APIs, so we can have a clear eye on our users' behaviour and interaction with our platform, so we can prioritise features and manage building iterations in a more user-oriented way, and our integration of choice is Mixpanel

Challenge we are trying to solve

Mixpanel NodeJs library's functions are built as callback and our code base is an ExpressJs app encapsulated in an AWS Lambda using the serverless framework "I'll share why in another post" and in our code base we are using async/await, And the question at that time was how to integrate the callback behaviour inside an async/await function?

What we did first πŸ‘‡


// Mixpanel Service
// path: /src/services/mixpanel-service.js

const mixpanel = require('mixpanel').init('<TOKEN>');

// We tried to wrap it with async/await to consist the
// codebase style and to avoid callback hell.
async function track(event, properties) {
  await mixpanel.track(event, properties)
}

module.exports = { track }

Enter fullscreen mode Exit fullscreen mode

// Customer Model
// path: /src/models/customer-model.js

// database interface instance
// we are using knex.js -> https://knexjs.org
const db = require('../configs/database-configs.js');

const mixpanel = require('../services/mixpanel-service.js');

class Customer extends User {
  constructor(fullName, email, address, brand) {
    super(fullName, email, address);
  }

  async create() {
    const { fullName, email, address, brand } = this;
    try {
      await db('customers').insert({ fullName, email, address, brand });
      await mixpanel.track('customer creation succeed', { fullName, email, address, brand });
    } catch (error) {
      await mixpanel.track('customer creation failed', { fullName, email, address, brand });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The above code did nothing in terms of tracking, and it throw no error and the function did not behave as an async/await function even after we wrapped it with async/await, because the execution is not ruled by the wrapper.

As a syntax and code execution sequence, there is nothing wrong, but the code execution behaviour does not match the expected behaviour.

The simple straightforward solution we considered was using promisify to force mixpanel-service to adapt the async/await ( returning promise ) behaviour by wrapping the track function with promisify to change it to a function that returns a promise.


// Mixpanel Service
// path: /src/services/mixpanel-service.js

const util = require('util');
const mixpanel = require('mixpanel').init('<TOKEN>');
const trackAsync = util.promisify(mixpanel.track);

// We tried to wrap it with async/await to consist the
// codebase style and to avoid callback hell.
async function track(event, properties) {
  await trackAsync(event, properties)
}

module.exports = { track }

Enter fullscreen mode Exit fullscreen mode

// Customer Model
// path: /src/models/customer-model.js

// database interface instance
// we are using knex.js -> https://knexjs.org
const db = require('../configs/database-configs.js');

const mixpanel = require('../services/mixpanel-service.js');

class Customer extends User {
  constructor(fullName, email, address, brand) {
    super(fullName, email, address);
  }

  async create() {
    const { fullName, email, address, brand } = this;
    try {
      await db('customers').insert({ fullName, email, address, brand });
      await mixpanel.track('customer creation succeed', { fullName, email, address, brand });
    } catch (error) {
      await mixpanel.track('customer creation failed', { fullName, email, address, brand });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The above code matched the expected output and logged the events inside our Mixpanel project's dashboard.

Takeaway notes:

  • Read the library that you want to use in your project and understand its behaviour and implementations.
  • Wrapping callback-based function in async/await block will not change that function behaviour if there is no promise implementation to that function
  • Learning by doing and researching is a great joy.

Sorry in advance for any grammar mistake or typo πŸ‘».
If you have any concerns or feedback I'm all earsπŸ‘‚.

cheers.

Top comments (0)