JavaScript Interview Question: Generator Function

JavaScript Interview Question: Generator Function

JavaScript is a popular programming language used for developing web applications. One of the key features of JavaScript is the use of generators. Generators are functions that can pause and resume their execution, allowing for more flexible and efficient code. In this article, we'll explore the basics of generators in JavaScript and how they work.

Generator Code Operation

To understand generator code operation, we first need to understand how regular functions work in JavaScript. Regular functions execute from start to finish and return a single value. However, generators are different. They can pause and resume their execution at any point and can return multiple values.

Generators are defined using the function* syntax, which indicates to the JavaScript engine that this is a generator function. When a generator function is called, it returns an iterator object that can be used to control the execution of the generator.

Here's an example of a simple generator function:

function* simpleGenerator() {
  yield 'Hello';
  yield 'World';
  return 'Done!';
}

This generator function has three statements: two yield statements and one return statement. The yield statement is used to pause the execution of the generator and return a value to the caller. The return statement is used to indicate that the generator has completed its execution and return a final value to the caller.

To use this generator function, we need to call it and get an iterator object:

const iterator = simpleGenerator();

Once we have the iterator object, we can start controlling the execution of the generator using the next() method:

console.log(iterator.next()); // { value: 'Hello', done: false }
console.log(iterator.next()); // { value: 'World', done: false }
console.log(iterator.next()); // { value: 'Done!', done: true }

As we can see, each call to the next() method causes the generator to resume its execution and return the next value using the yield statement. When the generator reaches the end of its execution, it returns the final value using the return statement.

Yield and Yield*

The yield statement is used to pause the execution of a generator and return a value to the caller. The yield* statement is used to delegate the execution to another generator or iterable object.

Here's an example of a generator function that uses the yield* statement:

function* delegatedGenerator() {
  yield 'Hello';
  yield* ['World', '!'];
  return 'Done!';
}

In this example, the yield* statement delegates the execution to an array. This means that each value in the array is returned to the caller using the yield statement.

To use this generator function, we need to call it and get an iterator object:

const iterator = delegatedGenerator();

Once we have the iterator object, we can start controlling the execution of the generator using the next() method:

console.log(iterator.next()); // { value: 'Hello', done: false }
console.log(iterator.next()); // { value: 'World', done: false }
console.log(iterator.next()); // { value: '!', done: false }
console.log(iterator.next()); // { value: 'Done!', done: true }

As we can see, the delegatedGenerator() function returns the values from the array using the yield statement.

Return Statement

The return statement is used to indicate that the generator has completed its execution and return a final value to the caller. This statement can be used in conjunction with the yield statement to return multiple values from the generator.

Here's an example of a generator function that uses the return statement:

function* returnGenerator() {
  yield 'Hello';
  yield 'World';
  return 'Done!';
}

In this example, the return statement is used to indicate that the generator has completed its execution and return a final value to the caller.

To use this generator function, we need to call it and get an iterator object:

const iterator = returnGenerator();

Once we have the iterator object, we can start controlling the execution of the generator using the next() method:

console.log(iterator.next()); // { value: 'Hello', done: false }
console.log(iterator.next()); // { value: 'World', done: false }
console.log(iterator.next()); // { value: 'Done!', done: true }

As we can see, the returnGenerator() function returns the values using the yield statement and returns the final value using the return statement.

Finally Statement

The finally statement is used to execute code after the generator has completed its execution, regardless of whether an exception was thrown or not. This statement is useful for cleaning up resources that were used by the generator.

Here's an example of a generator function that uses the finally statement:

function* finallyGenerator() {
  try {
    yield 'Hello';
    yield 'World';
    throw new Error('Something went wrong');
  } finally {
    console.log('Cleaning up resources');
  }
}

In this example, the finally statement is used to log a message after the generator has completed its execution, regardless of whether an exception was thrown or not.

To use this generator function, we need to call it and get an iterator object:

const iterator = finallyGenerator();

Once we have the iterator object, we can start controlling the execution of the generator using the next() method:

console.log(iterator.next()); // { value: 'Hello', done: false }
console.log(iterator.next()); // { value: 'World', done: false }
console.log(iterator.next()); // Error: Something went wrong
console.log(iterator.next()); // Cleaning up resources

As we can see, the finallyGenerator() function throws an exception using the throw statement, but the finally statement still gets executed.

Throw Statement

The throw statement is used to throw an exception from the generator. This statement can be used to handle errors in the generator.

Here's an example of a generator function that uses the throw statement:

function* throwGenerator() {
  try {
    yield 'Hello';
    yield 'World';
    throw new Error('Something went wrong');
  } catch (e) {
    console.log(e.message);
  }
}

In this example, the throw statement is used to throw an exception from the generator and handle it using the catch statement.

To use this generator function, we need to call it and get an iterator object:

javascriptCopy codeconst iterator = throwGenerator();

Once we have the iterator object, we can start controlling the execution of the generator using the next() method:

javascriptCopy codeconsole.log(iterator.next()); // { value: 'Hello', done: false }
console.log(iterator.next()); // { value: 'World', done: false }
console.log(iterator.throw(new Error('Something else went wrong'))); // Error: Something else went wrong

As we can see, the throwGenerator() function throws an exception using the throw statement, which is then caught and handled using the catch statement.

Conclusion:

Generator functions in JavaScript are functions that can be paused and resumed, allowing for the creation of iterators. The yield and yield* statements are used to pause the generator and return a value to the caller, while the return statement is used to indicate that the generator has completed its execution. The finally statement is used to execute code after the generator has completed its execution, regardless of whether an exception was thrown or not and also whether a return statement was used. Finally, the throw statement is used to throw an exception from the generator.


The content of this blog post was inspired by the JavaScript - Marathon Interview Questions Series 2023 course on Udemy