Event tracking with Mixpanel and NodeJs, The Callback in Async scope magic!
Tags: javascript, aws, node, serverless
Published: 2022-03-21 16:29:14

### 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](https://mixpanel.com/) and why we decided to use it at [Supahands](https://www.supahands.ai/), 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](https://mixpanel.com/)

### Challenge we are trying to solve
[Mixpanel](https://mixpanel.com/) NodeJs library's functions are built as [callback](https://nodejs.org/en/knowledge/getting-started/control-flow/what-are-callbacks/) 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function), And the question at that time was how to integrate the callback behaviour inside an async/await function?

What we did first 👇

```javascript

// 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 }

```
```javascript

// 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 });
    }
  }
}
```
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](https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original) 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.

```javascript

// 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 }

```
```javascript

// 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 });
    }
  }
}
```

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.