Collections, Generators, and Promises

  • Last updated on 21st Jul 2023

Introduction

Long ago, the council decided that Collections and Generators we’re necessary for ECMAScript 2015. Along with Map, Set, Weakmap, and WeakSet. But this article is focused on the first two, and combining them with Promises practically.

What are they?

Generators are functions that can be paused and resumed. They we’re introduced because it makes it easier for us to manage and understand asynchronous operations like fetching data from a server or waiting for user input. Because we’re allowed to pause and resume the execution of a function, we can write asynchronous code in a more sequential and intuitive way.

While Collections are a group of data structures to work with collections of data more efficiently. Some of this was covered previously my last article. But not, working with Sets. Sets and Maps are part of then-2015 introduced Collections data structures. They help us write more refined and deal with common data-related tasks.

Promises are functions that can represent two states, either complete or failure. It helps us deal with the period in which we’re waiting for something to happen, and let’s us avoid the headache of nested callbacks It’s useful to solve the racing condition problem as a by-product of concurrent programming challenges.

Generators and Collections

"use strict";

// Define artworks collection generator function
function* generateArtworks() {
    yield { id: 1, title: "Starry Night", artist: "Vincent van Gogh", year: 1889 };
    yield { id: 2, title: "Mona Lisa", artist: "Leonardo da Vinci", year: 1503 };
    yield { id: 3, title: "Vasily Eroshenko", artist: "Nakamura Tsune", year: 1920 };
    yield { id: 4, title: "The Persistence of Memory", artist: "Salvador Dalí", year: 1931 };
    // More artworks can be added here
}

// Define genres collection generator function
function* generateGenres() {
    yield "Impressionism";
    yield "Renaissance";
    yield "Renoir";
    yield "Surrealism";
    // More genres can be added here
}

// Create collections using generators
const artworksCollection = generateArtworks();
const genresCollection = generateGenres();

// Function to display artworks
function displayArtwork(artwork) {
    console.log(`Title: ${artwork.title}`);
    console.log(`Artist: ${artwork.artist}`);
    console.log(`Year: ${artwork.year}`);
    console.log("--------------------------");
}

// Function to display genres
function displayGenre(genre) {
    console.log(`Genre: ${genre}`);
    console.log("--------------------------");
}

// Explicitly iterate through artworks collection using next()
console.log("Artworks Collection:");
let nextArtwork = artworksCollection.next();
while (!nextArtwork.done) {
    displayArtwork(nextArtwork.value);
    nextArtwork = artworksCollection.next();
}

// Explicitly iterate through genres collection using next()
console.log("Genres Collection:");
let nextGenre = genresCollection.next();
while (!nextGenre.done) {
    displayGenre(nextGenre.value);
    nextGenre = genresCollection.next();
}

Above, we are using generators to create lists of artworks and art genres for an virtual art exhibition. The generateArtworks function lists famous paintings with details like title, artist, and year, while generateGenres lists different types of art styles. When we call these functions, they give us one item at a time working with yield.

We start by printing “Artworks Collection:” and then get to the first artwork using artworksCollection.next(). Which will give us an object with the artwork and a done property. We show the artwork using displayArtwork and keep getting the next artwork until done is true, meaning there are no more artworks.

We do the same for genres, printing “Genres Collection:” and getting each genre with genresCollection.next(), showing it with displayGenre, and repeating it until there are no more genres. It allows us to use yield and next() to handle one item at a time from the list. Which makes it easy to manage large collections without loading everything at once.

Promises

"use strict";

// Same code as before...

// Function to fetch and display dog images
async function fetchDogImages() {
  const response = await fetch("https://shibe.online/api/shibes?count=5");
  const dogImages = await response.json();
  dogImages.forEach((imageUrl) => console.log(imageUrl));
}

// Wrapper generator function to control execution flow
async function* exhibitionFlow() {
  const userResponse = prompt("Would you like to see the Dog Artwork section? (y/n)");

  if (userResponse.toLowerCase() === "y") {
    console.log("Presenting Dog Artwork:");
    yield await fetchDogImages(); // Yield control after fetching dog images
  } else {
    console.log("Skipping Dog Artwork section.");
  }

  // Continue with existing artwork and genre iterators
  console.log("Artworks Collection:");
  let nextArtwork = artworksCollection.next();
  while (!nextArtwork.done) {
    displayArtwork(nextArtwork.value);
    nextArtwork = artworksCollection.next();
  }

  console.log("Genres Collection:");
  let nextGenre = genresCollection.next();
  while (!nextGenre.done) {
    displayGenre(nextGenre.value);
    nextGenre = genresCollection.next();
  }
}

// Start the exhibition flow
(async () => {
  for await (const _ of exhibitionFlow()) {
    // Loop continues until exhibitionFlow generator is done
  }
})();

Above, we control the asynchronous art exhibition flow using generators and promises. The fetchDogImages function first uses fetch to retrieve dog image URLs as a promise, then uses await to wait for the response before converting it to JSON. It iterates through the URLs and logs them.

The important part lies in how the control flow is directed in the exhibitionFlow generator function. Immediately, it prompts the user for displaying the “Dog Artwork” section. If the user agrees (y), it calls fetchDogImages using yield await.

yield then pauses the generator until the dog image fetching is complete, then continues execution. Otherwise, it skips the dog artwork section.

The flow then continues by iterating through two separate iterators (artworksCollection and genresCollection) using a loop with for await...of.

In each iteration, it retrieves the next artwork/genre using next(), and displays it using the appropriate functions (displayArtwork or displayGenre), and repeats until the iterators are altogether exhausted.

Benefits

Like in our example, promises are able to handle asynchronous operations like fetching images, while waiting for a certain action to occur or data before proceeding.

Generators meanwhile control the flow by pausing execution at yield points, allowing for a more structured and readable way to manage asynchronous steps within the exhibition flow.

Conclusion, considerations

The practical performance cost with CPU’s that can process trillions of operations per second is negligible and we shouldn’t really worry about that since the performance cost is trivial. Even while resuming and pausing generators. This works similarly to our brain. Similar to the psychological concept of set-shifting.

Combining promises, generator functions, and collections let us sequentially handle asynchronous tasks while preserving control flow clarity despite JavaScript’s asynchronous nature.

Of course, there is more to this topic than what I wrote above, for more references checkout the below. As always, I hope you have a great day!

Resources