Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simulating a birthday party where pancakes are served - this gist describes a naïve approach (no parallel or aysnchronous processing, no use of pipelining) and a smart approach (where those mechanisms are employed). The code demonstrates the use of asynchronous generators in combination with promises.
// pancake party for my son's birthday
// I have promised him pancakes. Each pancake has to be baked, decorated (syrup, sugar, jam, ..) and sliced (in bite size sections)
const numberOfGuests = 8
// assuming each guest eats exactly three pancakes
const totalNumberOfPancakes = numberOfGuests * 3
const numberOfPans = 1
// times in milliseconds
const timeToBakeOnePancake = 3000;
const timeToDecoratePancake = 1200;
const timeToSliceAndDicePancake = 600;
const timeToEatPancake = 2700;
const timeSpeedUpFactor = 10;
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, Math.floor(milliseconds / timeSpeedUpFactor)))
}
const lg = (msg) => {
const d = new Date()
console.log(`${d.getSeconds()}.${Math.round(d.getMilliseconds() / 100)} - ${msg}`)
}
eatPancake = async function (guest, pancake) {
lg(`Guest ${guest} is happily eating ${JSON.stringify(pancake)}`)
await sleep(timeToEatPancake).then(() => {
lg(`Guest ${guest} is done eating ${JSON.stringify(pancake)}.`);
})
return
}
// this function represents the baking process for a single pancake;
// baking is represented by sleep() and a simplistic pancake object is returned
bakeOnePancake = async function (index) {
return sleep((0.9+0.2*(Math.random())) * timeToBakeOnePancake).then(() => { return { index: index } })
}
bakeAllPancakes = async function (numberOfPancakes) {
let stack = []
while (stack.length < numberOfPancakes) {
lg(`.. baking pancake ${stack.length+1}`)
var pancake = await bakeOnePancake(stack.length)
stack.push(pancake)
}
return stack
}//bakeAllPancakes
const toppings = ['syrup', 'chocolate sprinkles', 'strawberry jam']
decoratePancakes = async function (stackOfPancakes) {
for (pancake of stackOfPancakes) {
lg(`.. decorating pancake ${pancake.index}`)
await sleep(timeToDecoratePancake)
pancake.topping = toppings[Math.floor(Math.random() * 3)];
}
return stackOfPancakes
}//decoratePancakes
sliceAndDicePancakes = async function (stackOfPancakes) {
for (pancake of stackOfPancakes) {
lg(`.. slicing and dicing pancake ${pancake.index}`)
await sleep(timeToSliceAndDicePancake)
pancake.sliced = `${4 + Math.floor(Math.random() * 3)} pieces`;
}
return stackOfPancakes
}//sliceAndDicePancakes
// fully sequential approach
party = async function () {
var startTime = Date.now();
// first get the full stack of pancakes
var pancakes = await bakeAllPancakes(totalNumberOfPancakes)
// then decorate all pancakes
var decoratedPancakes = await decoratePancakes(pancakes)
// finally slice and dice all of them, turning them into ready to eat pancakes
var readyToEatPancakes = await sliceAndDicePancakes(decoratedPancakes)
// finally, all guests get to eat the pancakes, in three rounds
var firstPancakeOnPlateTime = Date.now();
for (var eatingRound = 0; eatingRound < 3; eatingRound++) {
for (var guest = 0; guest < 8; guest++) {
// hand pancake to guest and wait for the guest to finish eating
await eatPancake(guest + 1, readyToEatPancakes.pop())
}// guests
}// eatingRounds
var endTime = Date.now();
console.log(`Time from start to first pancake on plate ${(firstPancakeOnPlateTime - startTime) / 1000}`)
console.log(`Time from start to finish ${(endTime - startTime) / 1000}`)
}// party
// go and have that party!
party()
// pancake party for my son's birthday
// I have promised him pancakes. Each pancake has to be baked, decorated (syrup, sugar, jam, ..) and sliced (in bite size sections)
// - the process is pipelined: from baking to decorating to slicing to eating is a pipelined serie of handovers
// note however that there is no parallellism in the pipeline: the eating takes place sequentially and since the eating drives the entire pipeline, everything else is sequential as well
// What we can do is have each node in the pipeline build up its own little cache of ready product and yield to order
// that way for example we can start cleaning the pans earlier on and send the cooks home
const numberOfGuests = 8
// assuming each guest eats exactly three pancakes
const numberOfPancakesPerGuest = 3
const totalNumberOfPancakes = numberOfGuests * numberOfPancakesPerGuest
// the number of pans at this point does not change the time either start to finish or to first pancake on plate
const numberOfPans = 4
// times in milliseconds
const timeToBakeOnePancake = 3000;
const timeToDecoratePancake = 1200;
const timeToSliceAndDicePancake = 600;
const timeToEatPancake = 2700;
const timeSpeedUpFactor = 10;
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, Math.floor(milliseconds / timeSpeedUpFactor)))
}
const lg = (msg) => {
const d = new Date()
console.log(`${d.getSeconds()}.${Math.round(d.getMilliseconds() / 100)} - ${msg}`)
}
// the function that describes eating the pancake by a guest
// the promise of pancake (an empty plate with cutlery?) is the input
// as soon as the promise materializes into a pancake
// a message is logged and the guest starts eating; when done eating (representedby sleep) another message is logged
eatPancake = function (guest, pancakePromise) {
return pancakePromise.then(
(pancake) => {
if (firstPancake) { firstPancake = false; firstPancakeOnPlateTime = Date.now(); }
lg(`Guest ${guest} is happily eating ${JSON.stringify(pancake)}`);
return sleep(timeToEatPancake).then(() => {
lg(`Guest ${guest} is done eating ${JSON.stringify(pancake)}.`);
})
})
}//eatPancake
// this function represents the baking process for a single pancake;
// baking is represented by sleep() and a simplistic pancake object is returned
bakeOnePancake = async function (index) {
return sleep((0.9+0.2*(Math.random())) * timeToBakeOnePancake).then(() => { return { index: index } })
}
bakeAllPancakes = async function* (numberOfPancakes) {
let stacksize = 0
var freshPancakes;
while (stacksize< numberOfPancakes) {
let bakingNow = []
for (var pan = 0; pan < numberOfPans; pan++) {
lg(`.. baking pancake ${stacksize + pan + 1} in pan ${pan}`)
// add promise for pancake to bakingNow collection
bakingNow.push(bakeOnePancake(stacksize + pan + 1))
}// for pans
await Promise.all(bakingNow).then((pancakes) => {
lg(` .. round of baking pancakes complete, all pans done. `)
freshPancakes = pancakes
});
// yield all pancakes - executed after Promise.all
for (pancake of freshPancakes) yield pancake;
stacksize = stacksize + freshPancakes.length
freshPancakes = null;
lg(`.. all pans free, next round`)
}// while
}//bakeAllPancakes
const toppings = ['syrup', 'chocolate sprinkles', 'strawberry jam']
decoratePancakes = async function* (stackOfPancakes) {
for await (pancake of stackOfPancakes) {
lg(`.. decorating pancake ${pancake.index}`)
await sleep(timeToDecoratePancake)
pancake.topping = toppings[Math.floor(Math.random() * 3)];
yield pancake
}
}//decoratePancakes
sliceAndDicePancakes = async function* (stackOfPancakes) {
for await (pancake of stackOfPancakes) {
lg(`.. slicing and dicing pancake ${pancake.index}`)
await sleep(timeToSliceAndDicePancake)
pancake.sliced = `${4 + Math.floor(Math.random() * 3)} pieces`;
yield pancake
}
}//sliceAndDicePancakes
var firstPancakeOnPlateTime
var firstPancake = true;
party = async function () {
var startTime = Date.now();
// pipeline the pancakes from baking through decorating and slicingNDicing to eating
// readyToEatPancakes is an iterable - a promise with multiple deliveries
readyToEatPancakes = sliceAndDicePancakes(decoratePancakes(bakeAllPancakes(totalNumberOfPancakes)))
// in three eating rounds
for (var eatingRound = 0; eatingRound < numberOfPancakesPerGuest; eatingRound++) {
var eatingGuests = []
// we get pancakes to all guest as quickly as possible
for (var guest = 1; guest <= numberOfGuests; guest++) {
var pk = readyToEatPancakes.next()
eatingGuests.push(eatPancake(guest, pk))
}// guests
lg(`All guests have been handed a pancake promise`)
// and then we wait for all guests to have finished eating their pancake
await Promise.all(eatingGuests).then((values) => {
lg(`.. eating round ${eatingRound + 1} complete.`)
})
}
var endTime = Date.now();
console.log(`Time from start to first pancake on plate ${(firstPancakeOnPlateTime - startTime) / 1000}`)
console.log(`Time from start to finish ${(endTime - startTime) / 1000}`)
}// party
// go and have that party!
party()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.