Using JavaScript generators to optimize APIs

I'm a software developer and computer scientist who loves building things.
Search for a command to run...

I'm a software developer and computer scientist who loves building things.
No comments yet. Be the first to comment.
The Interface Segregation Principle is the I in SOLID design principles. It states that types should not implement methods unrelated to their operations. In essence, interfaces should be designed so that the implementing types are not forced to imple...

The project management iron triangle applied to software development
Errors in golang are vastly different from errors in JavaScript. In this post, we will implement error handling for the events project we started building in the first post . That means, handling errors within the module itself as well as errors fr...

If you already know how to use a programming language, when learning a new language it is only natural to attempt to draw comparisons. This post is the first in a series, we'll attempt to learn Golang by re-implementing Events APIs in Go. What are th...

This post is inspired by this wonderful post by Evelyn Stender. This post does not explain what a generator is or basic usage, Evelyn's post does a good job of that. Instead, in this post, we will demonstrate how generators can be useful for streaming either a very large dataset or dataset requiring significant computation.
Say, we have a function, that returns a generator for getting accounts:
function* getAccounts(totalCount = 0) {
while (totalCount > 0) {
--totalCount;
// assume complex computation for account balance.
yield { userId: totalCount + 1, balance: Math.random() * 100 };
}
}
Notice the use of yield, that way the function doesn't wait till all user accounts are processed before returning results. Next step, we need to get these accounts and do something with them; in this case, we will be returning them to a client.
app.get('/accounts', (req, res) => {
const accountsGenerator = getAccounts(req.query.count);
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.setHeader('Transfer-Encoding', 'chunked');
writeData(res, accountsGenerator);
});
function writeData(response, generator) {
var account = generator.next();
if (true === account.done) {
response.end();
} else {
response.write(JSON.stringify(account.value));
setTimeout(function () {
writeData(response, generator);
}, 0);
}
}
This is an express route that streams accounts gotten from the generator (using a helper function). Putting it all together,
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
app.use(bodyParser.json());
function* getAccounts(totalCount = 0) {
while (totalCount > 0) {
--totalCount;
yield { userId: totalCount + 1, balance: Math.random() * 100 };
}
}
app.get('/get-accounts', (req, res) => {
const accountsGenerator = getAccounts(req.query.count);
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.setHeader('Transfer-Encoding', 'chunked');
writeData(res, accountsGenerator);
});
function writeData(response, generator) {
var account = generator.next();
if (true === account.done) {
response.end();
} else {
response.write(JSON.stringify(account.value));
setTimeout(function () {
writeData(response, generator);
}, 0);
}
}
app.listen(port, () => console.log(`App listening on port ${port}!`));
There are several improvements we can make to this solution (using correct headers, removing the recursive function, making the streaming of response better etc), but for simplicity's sake this works. For advantages of using this method instead of loading all accounts into memory before streaming see linked post.