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*
andyield
) 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 functionfn
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 thefunction.name
property to get the original function’s name for logging (or just use a hardcoded name if undefined).
Instructions: DefinelogExecution(fn)
inside which you return an inner function (closure) that wraps the call tofn
. 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.
- Print a message before calling
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 callingevenIter.next()
five times and collecting the.value
, or by using afor...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 usingasync/await
.- Write a function
fetchData()
that returns a new Promise. Inside the promise executor, usesetTimeout
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 atry...catch
block to callfetchData()
withawait
. Upon success, log the result (same message as before). If an error is thrown (you could modifyfetchData
to reject for testing), catch it and log an error message. Call thisgetData()
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()
(orgetData()
) to show that the code does not block during the asynchronous wait. For example: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).console.log("Fetching data..."); fetchData().then(data => console.log("Got data:", data)); console.log("This log happens before data is received, demonstrating async.");
- (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.
- Write a function
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 & Features | Points |
---|---|
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
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.