Everything But The Kitchen Async

Back in the days before the era of covid-19 (and before my free time was spent coding), I worked in an office of about 200 people. During the winter, I always loved baking large batches of cookies and bringing them into share. Doing this accomplishes several things all in one fell swoop:

  • You get to pick your favorites
  • Everyone loves you for the day
  • You get to show off your sweet baking skills
  • You have an excuse to eat 6 cookies at your desk in one sitting

Sounds amazing. But, baking cookies from scratch is no easy skill to develop. You quickly learn that timing is everything and measurements are key. Mixing things too long in the mixer, leaving the cookies in for ten extra seconds, adding too much baking powder… All of these things will lead to disaster, and massive disappointment for your fans. Thats why online baking recipes start off with massively long anecdotal stories about some off topic conversation the writer had at the post office last Sunday or some gossip about the neighbors dog. Bakers are meticulous, and certainly a little bit crazy.

Though, this does not detract from the point: Timing and procedure have almost more of an impact on your baking than the actual ingredients you put into it. I don’t care if you are using the finest ground nutmeg from your grandma’s personal tree in Jamaica, if you don’t prepare the rest of your ingredients in the correct order your cookies will end up in the trash. Let me tell you some examples of how to bake:

  • Scoop two cups of flour into a bowl, crack two eggs on top, bake for 12 minutes
  • Open a container of flour, open a container of salt, open a container , run an empty kitchen aid mixer for 2 minutes, leave the oven on for 10 minutes with nothing in it
  • Mix together baking mix in a bowl, turn the oven on and immediately put the mix into the oven and leave it in there until it reaches 350 degrees
  • Mix flour, butter, salt, sugar, molasses, and some spices, separate onto a baking sheet, bake for 13 minutes, then crack two eggs on top and pour oil over the cookies

Notice how none of these make any sense at all. You will also find that if you follow any of these instructions, you would be starting a part of the process before another part was complete. This is also what happens when you run Asynchronous functions without telling your program that you are waiting on them to complete.

When dealing with dynamic information and also when handling information to and from a database or storage location, you are constantly dealing with Asynchronous actions. Simply put, it takes time to get information out of where it is being held. This is perfectly normal, and everything will go as planned… as long as you plan for it. This can be done in many different ways, but I personally think that the most readable and functional way of dealing with Async actions is with Promises.

There are many different forms and origins of promises in javascript as it not only has promises built in, but there are also libraries such as Bluebird, and some libraries, such as fs (node filesystem) that have alternate library extensions that convert their methods into promises. The main goal of promises is to take synchronous actions that require callbacks and turn them into objects that represent the that represent the value of a function. In other terms, they are a of a returned value of a function or a sequence of functions. If we were to run an artisanal cookie shop using synchronous functions with callbacks, they might look like this:

const bake = require('baking-techniques');const Mixer = (type, callback) => {
bake.mixIngredients(type.ingredients, (err, mixed) => {
if (err) {
throw ('Mixing Machine Broke');
} else {
callback(null, {mix: mixed, type: type});
}
});
};
const Oven = (cookieData, callback) => {
let bakeSpecs = bake.ovenSpecs(cookieData.type.name)
bakeSpecs(cookieData.mix, (err, cookies) => {
if (err) {
throw ('Oven Machine Broke');
} else {
callback(null, cookies);
}
});
};
const Package = (cookies, callback) => {
bake.package(cookies, (err, package) => {
if (err) {
throw ('Package Machine Broke');
} else {
callback(null, package);
}
});
};

A ‘GET’ request from a client to our shop might look like this:

getCookie: function(callback) {
return $.ajax({
url: `${this.url}`,
type: 'GET',
dataType: 'Packaged Cookie',
success: callback
});
},

In this case we would send a request for a cookie, and upon successfully making this request (confirming that the cookie store is open to receiving requests for cookies), the cookie store would send the packaged cookie through the callback to the client side of this system so that it could be delivered to them. Great! Customer satisfaction. But this only works if we already have packaged cookies ready to go. If this was called synchronously and we didn’t have any inventory, this would likely return in an error. Or worse, an empty cookie box. Sad times. So, let’s account for this possibility by making sure our get requests route to promises instead of a pipeline of synchronous callbacks:

const bake = require('baking-techniques');
const Promise = require('bluebird);
const bakePrm = Promise.promisifyAll(bake);
const createCookie = (type, callback) => {
return bakePrm.mixIngredients(type)
.then(cookieData => {
let bakeSpecs = bakePrm.ovenSpecs(cookieData.type.name);
return bakeSpecs(cookieData.mix);
})
.then(cookies => {
return bakePrm.package(cookies);
})
.then(package => {
callback(null, package)
})
.catch(error => callback(error))
}

So, after using the bluebird library to ‘promisify’ all of the baking methods inside of the baking-techniques library, we were able to create a promise that can interact with server requests. This ensures that each action waits for the last to complete before starting. Because of this, we won’t find ourselves synchronously pulling cookie boxes from an unpopulated cookie inventory, or making any of the wacky mistakes stated earlier in this post. Now, all that’s left is keeping customers entertained while we wait for the next batch to load…er, bake.