Faster API calls using WebSockets without having to subscribe to updates

Avraham David Gelbfish
2 min readSep 7, 2018

--

The goal is simple to manage, fast, and concurrent api calls, for when subscriptions aren’t necessary and are overhead.

Here’s the code, explanation to follow:

client:

class DataStreamPromisifier {
constructor() {
this.pending = {}
}

getPromise(id) {
return new Promise((resolve, reject) => {
this.pending[id] = data => data.error ? reject(data.error) : resolve(data.responses)
})
}

pushData(id, data) {
if (typeof this.pending[id] !== "function") {
console.log("nothing to do for " + id)
return
}
this.pending[id](data)
delete this.pending[id]
}
}

const dataStreamPromisifier = new DataStreamPromisifier()
const uuidv4Regex = //unimportant
//primus is our WebSocket connector
primus.on("data", data => {
if (!uuidv4Regex.test(data.requestId)) return
dataStreamPromisifier
.pushData(data.requestId, data)
});
//the parameter schema of this function can be domain specific
const getRemoteData = async (type, payload) => {
let requestId = uuidv4()
let dataPromise = dataStreamPromisifier.getPromise(requestId)
primus.write({
requestId: requestId,
type: type,
payload: payload
});
return await dataPromise
}

server:

const primus = Primus.createServer(function connection(spark) {
spark.on("data", async data => {
if (data.requestId === undefined) return
if
(data.type === "datarequest") {
const response = await processPayload(data.payload)
spark.write({requestId: requestId, responses: response})
} else {
spark.write({requestId: requestId, error: {code: 500, message: "an error occurred"}})
}
})
}, {port: 18080, transformer: 'engine.io'});

So the effect is an async function `getRemoteData()` that’s gets data using async-await, while all calls to that function go over the same websocket.

This is faster because it uses websockets and doesn’t have the overhead of a full HTTP request for every call.

And when there’s no need for a subscription setup such as when the data is only necessary on demand, this avoids subscriptions and the code is more readable (it flows).

So, what’s going on here?

“gray and white audio mixer” by Michael Weibel on Unsplash

We want to call a function and have it return when it gets its response from the server.

First we’ll need to be able to awaita response from a server. For that, we have class DataStreamPromisifier() which has a getPromise()method that gives promises and resolve() ‘s them when data identified by requestId comes back from the server. Once you have a Promise you can await it.

DataStreamPromisifier gets fed all data directly from the websocket.

So our function is as simple as 1, 2, 3:

  1. Generate a requestId
  2. Get a promise for that requestId from the Data Stream Promisifier
  3. Send a payload with the requestIdto the websocket data stream
  4. whoops, forgot about this one, return a promise to the data.

On the server it’s just about making sure we respond with the requestId attached.

return Promise.resolve(“The End”)

Footnote: The Promisifier could be improved to include a max TTL on the request using Promise.race() with timeout promise.

Thanks for reading!

--

--