Skip to Content
Lab 4

Advanced Functions and Asynchronous Programming

Overview and Objectives

This lab focuses on advanced function concepts, including higher-order functions (decorators), generators, iterators, and asynchronous programming with Promises and async/await. Learners implement a function decorator to add logging, create generators to yield sequences, and simulate asynchronous operations with proper error handling. By the end of this lab, learners will be able to:

  • Write higher-order functions that take other functions as arguments or return functions (function decorators or wrappers).
  • Define and use generator functions (using function* and yield) to produce sequences of values, and understand the iterator protocol.
  • Utilize iterators (implicitly via generators or manually) to iterate through custom sequences.
  • Implement asynchronous operations using callbacks, Promises, and/or async/await.
  • Understand how to handle the results of asynchronous operations without blocking execution.

Tasks and Instructions

This lab will cover three advanced topics: function decorators (higher-order functions), generators/iterators, and asynchronous programming. Each part is separate; you can tackle them in any order.

Task 1: Function Decorator (Higher-Order Function)

  • Create a higher-order function that serves as a decorator to enhance other functions. For example, build a function logExecution that takes a function fn as an argument and returns a new function. The returned function should:
    • Print a message before calling fn (e.g., "Calling function <name>"),
    • Call the original function fn and capture its return value,
    • Print a message after calling fn (e.g., "Function <name> returned <value>"), then
    • Return the value from fn.
      Use the function.name property to get the original function’s name for logging (or just use a hardcoded name if undefined).
      Instructions: Define logExecution(fn) inside which you return an inner function (closure) that wraps the call to fn. Then test this decorator:
    • Define a simple function, e.g., function add(a, b) { return a + b; }.
    • Create a decorated version: const wrappedAdd = logExecution(add);.
    • Call wrappedAdd(5, 3) and observe the console logs and return value. It should log messages before and after, and yield the correct sum result.
    • Try another function, e.g., function square(n) { return n * n; }, and decorate it to verify the decorator works for any function.

This demonstrates how a decorator can inject additional behavior (logging in this case) around an existing function.

Task 2: Generators and Iterators

  • Define a generator function evenNumbers() that yields an infinite sequence of even numbers starting from 0. In the function, use an infinite loop (while(true)) and a yield statement to yield the current even number, then increment by 2.
  • Use the generator to obtain an iterator: const evenIter = evenNumbers();. Retrieve the first five even numbers from the iterator. You can do this by calling evenIter.next() five times and collecting the .value, or by using a for...of loop and breaking after five iterations. Log these first five even numbers to confirm the generator works (expected output: 0, 2, 4, 6, 8).
  • Modify or create another generator, for example idGenerator(), that yields an incrementing ID starting from 1 each time it is called. Test it by getting a few values (e.g., 1, 2, 3, …). This can be similar to the even number generator but simpler (just yield i++ each time).
  • (Optional) Demonstrate using a generator to iterate over a custom object or data structure. For instance, if you have an array of products or names, you could write a generator that yields each name. However, since generators themselves provide an iterator, the key learning is covered with the even number example. If feeling adventurous, implement a custom iterable: create an object with a [Symbol.iterator] method that returns an iterator (or generator) to iterate its internal data. For example, an object that wraps an array and makes it iterable. This optional step can deepen understanding but is not required.

Note: Ensure to reset or recreate the generator if you need to iterate from the beginning again, as once a generator function’s iterator is exhausted (or at a certain point), continuing to call .next() will continue from where it left off.

Task 3: Asynchronous Programming (Callbacks, Promises, Async/Await)

  • Simulate an asynchronous operation and handle its result using both a Promise with .then and using async/await.
    • Write a function fetchData() that returns a new Promise. Inside the promise executor, use setTimeout to simulate a delay (e.g., 1 second). After the timeout, resolve the promise with some data, for example an object or simple value like "Hello World" or { success: true }. (In a real scenario this could be data fetched from a server, but here we will just simulate.)
    • Using the Promise: Call fetchData() and attach a .then() handler that receives the resolved value and logs a message, e.g., "Promise resolved with: <data>". Also attach a .catch() to handle any potential rejection (even if you only resolve in this case, it’s good practice). Test that after ~1 second, your message appears with the correct data.
    • Using async/await: Create an async function (for example, async function getData()), and inside it use a try...catch block to call fetchData() with await. Upon success, log the result (same message as before). If an error is thrown (you could modify fetchData to reject for testing), catch it and log an error message. Call this getData() function to demonstrate it works (it will return a promise; calling it initiates the internal code).
    • While testing, log something immediately before and after calling fetchData() (or getData()) to show that the code does not block during the asynchronous wait. For example:
      console.log("Fetching data..."); fetchData().then(data => console.log("Got data:", data)); console.log("This log happens before data is received, demonstrating async.");
      In the output, you should see “Fetching data…”, then immediately “This log happens before data is received…”, and after 1 second “Got data: …” from the promise resolution. Similarly for the async/await version (which under the hood does the same thing).
    • (Optional) For further exploration, if you have access to a browser environment, you could use fetch() to retrieve real data from a public API, but that requires network and is not necessary for this lab. The goal here is to understand the mechanics of promises and async functions.

Submission Criteria

Via a GitHub repository, submit the following via Canvas:

  • Submit your code file lab4.js containing solutions for all parts: the decorator function and its usage, the generator functions and their outputs, and the asynchronous function with both promise and async/await handling. Clearly separate each part of the lab in your code with comments (e.g., // Part 1: Decorator, etc.).
  • Ensure that the console logs demonstrate the functionality: For the decorator, the console should show messages before and after function calls; for the generator, it should list the even numbers (and any other sequence you test); for the async part, it should show the order of messages (“Fetching…” and “This happens before…” appearing before the fetched data result).
  • The code should run without errors. If you encounter issues with one approach (say async/await in a non-Top-Level context), you can rely on the Promise .then() version, but ideally both methods should be shown. If you cannot get a part to work, include it commented out with an explanation to potentially get partial credit.

Grading Rubric

Criteria & FeaturesPoints
Decorator function (Higher-order): Implemented a higher-order decorator (e.g., logExecution) that wraps another function, and demonstrated it with at least one example. Logs before/after and returns correct result.15 points
Generator function: Created a generator (e.g., evenNumbers) that yields a sequence of values correctly. Retrieved values from the generator (via .next() or iteration) and output the expected sequence.15 points
Iterator usage: Properly utilized the generator’s iterator (or a custom iterable) to loop through or access multiple values. Stopped after a certain number of yields (preventing infinite loop). This can be combined with the generator criterion by demonstrating iteration.5 points
Asynchronous with Promise: Implemented an asynchronous operation using a Promise (with setTimeout or similar) and handled its result with .then() (and .catch). The non-blocking nature is demonstrated via log ordering.10 points
Async/Await usage: Shown an alternative handling of the asynchronous operation using async/await (in a function), including proper use of try...catch for error handling. Output confirms the awaited result and retains non-blocking behavior.10 points
Code correctness and style: The code is well-organized, commented, and runs without runtime errors. The output from each part of the lab is clearly visible and labeled. Minor syntax errors or logic issues can be forgiven if the intention is clear and most parts work.5 points

Total Points: 60 points

Note

Each part of this lab (decorators, generators, async) is graded independently. You will receive points for the portions that are correctly implemented even if one part is incomplete. For example, if your generator works but the async part has issues, you will still earn the points for the generator section.