Skip to content

Implementing asynchronous operations in javascript / typescript

This entry is part 2 of 4 in the series Musings from an Open Source Squad

Implementing asynchronous operations in javascript / typescript?

Asynchronous programming is very similar! . This solution works perfectly when the chef gets the orders properly propagated from the customer. But let’s say due to a high demand and crowd at the restaurant if the orders get mixed up, a customer may end up getting pasta instead of pizza :).

This is exactly what happened in our async API implementation. This issue manifested itself by how we passed our objects by reference in our async functions. In this article, I’d like to share what we learned so that others can have a head start working through asynchronous APIs.

Let’s have a look at this problem with a simple example below.

Problem:

The following is an example of a handler function for an API operation.

async function sampleHandler(context) {
    (async () => {
        await new Promise(resolve => setTimeout(resolve, 1000));
        console.log(context.request.body.name)
    })();
    context.response.code = 200
}

// A sample context object
const sampleContext = {
    request: {
        body: {
            name: 'Alice'
        }
    },
  response: {}
}
sampleHandler(sampleContext)

// Mutating the context object by modifying the request body
sampleContext.request.body.name = 'Bob'
sampleHandler(sampleContext)

To Run Click Here
The output of the above program is like this

Bob
Bob

Instead of

Alice
Bob

In the above example, the sampleHandler API async function returns an HTTP 200 sync response and prints the request body afterwards via an anonymous internal async function. 

The above example is with two simultaneous async function calls. The same issue occurs if there are simultaneous requests to the API server and the API library passes a mutated context object instead of a fresh one. In that case with the above example handler function may print the wrong body for a different request. This is because the context object is being passed by reference to the async function while it is mutated elsewhere.

Solution:

We can solve this problem by creating a deep copy of the required data from the context passing it down to the async function as per below.

async function sampleHandler(context) {
    const name = context.request.body.name;
    (async () => {
        await new Promise(resolve => setTimeout(resolve, 1000));
        console.log(name)
    })();
    context.response.code = 200
}

// A sample context object
const sampleContext = {
    request: {
        body: {
            name: 'Alice'
        }
    },
  response: {}
}
sampleHandler(sampleContext)

// Mutating the context object by modifying the request body
sampleContext.request.body.name = 'Bob'
sampleHandler(sampleContext)

To Run Click Here

If you need to pass down an object, you can use spread operator (…), although it is not a deep copy and copies the parameters in first level but it should be sufficient for most cases. We may use some expensive deep copying functions but at the cost of some resource utilization.

Takeaways

We ran into this situation when using the “Koa” API library which was solved by using the above method. Hopefully the information I’ve shared here will be useful and being aware of the common pitfalls can save you significant time while implementing / debugging the asynchronous APIs.

Contact the INFITX Team