阿里云主机折上折
  • 微信号
Current Site:Index > The yield keyword

The yield keyword

Author:Chuan Chen 阅读数:24677人阅读 分类: JavaScript

Basic Concepts of the yield Keyword

The yield keyword, introduced in ECMAScript 6, can only be used within Generator functions. This keyword pauses the execution of the function and returns the value of the expression following yield as the value property of the returned object. Generator functions, through yield, enable the ability to pause and resume execution, which is a fundamental aspect of asynchronous programming in JavaScript.

function* generatorExample() {
  yield 1;
  yield 2;
  return 3;
}

const gen = generatorExample();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }

Relationship Between Generator Functions and yield

Generator functions, denoted by the function* syntax, are the only context where the yield keyword can be used. Attempting to use yield in a regular function will result in a syntax error. When a Generator function is called, it does not execute immediately but instead returns an iterator object. The function body's code is executed step-by-step by calling the iterator's next() method.

function* idGenerator() {
  let id = 1;
  while(true) {
    yield id++;
  }
}

const gen = idGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

Pause and Resume Mechanism of yield

The most powerful feature of the yield keyword is its ability to pause function execution and resume it later. When execution reaches a yield expression, the function's execution context (including local variables, parameters, and temporary values) is preserved. The next call to next() resumes execution from where it was paused.

function* pauseAndResume() {
  const a = yield 'First pause';
  console.log(a); // 'Value passed on resume'
  const b = yield 'Second pause';
  console.log(b); // 'Another value'
}

const gen = pauseAndResume();
console.log(gen.next()); // { value: 'First pause', done: false }
console.log(gen.next('Value passed on resume')); // { value: 'Second pause', done: false }
console.log(gen.next('Another value')); // { value: undefined, done: true }

Return Value of yield Expressions

The yield expression itself also has a return value, which is determined by the argument passed to the next() method. The argument passed to next() becomes the return value of the previous yield expression. The argument passed to the first next() call is ignored since there is no preceding yield expression.

function* yieldReturnValue() {
  const first = yield 'First yield';
  console.log(first); // 'Value from first next'
  const second = yield 'Second yield';
  console.log(second); // 'Value from second next'
}

const gen = yieldReturnValue();
gen.next(); // Starts the generator, ignores the argument
gen.next('Value from first next');
gen.next('Value from second next');

yield* Delegating to Other Generators

The yield* expression is used to delegate to another Generator function or iterable object. It allows embedding one Generator function within another, making code organization clearer.

function* generatorA() {
  yield 'a';
  yield 'b';
}

function* generatorB() {
  yield 'x';
  yield* generatorA();
  yield 'y';
}

const gen = generatorB();
console.log(gen.next().value); // 'x'
console.log(gen.next().value); // 'a'
console.log(gen.next().value); // 'b'
console.log(gen.next().value); // 'y'

yield and Asynchronous Programming

The yield keyword introduced new approaches to asynchronous programming in JavaScript. Combined with Promises, it enables writing asynchronous code in a synchronous style, known as "coroutines." Although async/await has become the more mainstream solution, understanding yield in asynchronous programming remains valuable.

function runGenerator(generatorFunc) {
  const gen = generatorFunc();
  
  function handle(result) {
    if (result.done) return Promise.resolve(result.value);
    return Promise.resolve(result.value)
      .then(res => handle(gen.next(res)))
      .catch(err => handle(gen.throw(err)));
  }
  
  return handle(gen.next());
}

function* asyncGenerator() {
  try {
    const result1 = yield Promise.resolve('First async operation');
    console.log(result1); // 'First async operation'
    const result2 = yield Promise.resolve('Second async operation');
    console.log(result2); // 'Second async operation'
    return 'Done';
  } catch (error) {
    console.error(error);
  }
}

runGenerator(asyncGenerator).then(finalValue => {
  console.log(finalValue); // 'Done'
});

yield in the Iterator Protocol

Generator functions return objects that implement the iterator protocol, making it easy to create custom iterators. The yield keyword is particularly useful here, enabling on-demand generation of sequence values without precomputing all values.

function* fibonacci() {
  let [prev, curr] = [0, 1];
  while (true) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5
console.log(fib.next().value); // 8

yield and Error Handling

Generator functions can use try...catch blocks to handle errors, including those thrown at yield expressions. The iterator's throw() method allows external error throwing into the Generator function.

function* errorHandling() {
  try {
    yield 'Normal execution';
    yield 'This line will also execute';
  } catch (error) {
    yield `Caught error: ${error}`;
  }
}

const gen = errorHandling();
console.log(gen.next().value); // 'Normal execution'
console.log(gen.throw('External error').value); // 'Caught error: External error'
console.log(gen.next().value); // undefined

Practical Use Cases of yield in Projects

In real-world projects, yield and Generator functions have several practical applications, such as implementing infinite scroll loading, processing large datasets in chunks, and creating state machines. These scenarios leverage yield's pause-and-resume capability.

// Chunk processing of large arrays
function* chunkArray(array, chunkSize) {
  for (let i = 0; i < array.length; i += chunkSize) {
    yield array.slice(i, i + chunkSize);
  }
}

const bigArray = new Array(1000).fill().map((_, i) => i);
const chunkGenerator = chunkArray(bigArray, 100);

for (const chunk of chunkGenerator) {
  console.log(`Processing ${chunk.length} elements`);
  // Process each chunk here
}

yield and Recursive Generators

The yield* expression handles recursive Generator functions well, enabling concise representation of recursive data structures or algorithms.

function* traverseTree(node) {
  if (!node) return;
  yield node.value;
  if (node.left) yield* traverseTree(node.left);
  if (node.right) yield* traverseTree(node.right);
}

const tree = {
  value: 1,
  left: {
    value: 2,
    left: { value: 4 },
    right: { value: 5 }
  },
  right: {
    value: 3,
    left: { value: 6 },
    right: { value: 7 }
  }
};

for (const value of traverseTree(tree)) {
  console.log(value); // Outputs 1, 2, 4, 5, 3, 6, 7 in sequence
}

Performance Considerations for yield

While Generator functions and yield provide powerful control flow capabilities, they should be used cautiously in performance-sensitive scenarios. Each yield and next() call incurs some overhead, which may become a bottleneck in high-frequency iteration cases.

// Performance test: Regular loop vs. Generator
function regularLoop(n) {
  let sum = 0;
  for (let i = 0; i < n; i++) {
    sum += i;
  }
  return sum;
}

function* generatorLoop(n) {
  for (let i = 0; i < n; i++) {
    yield i;
  }
}

function sumWithGenerator(n) {
  let sum = 0;
  for (const num of generatorLoop(n)) {
    sum += num;
  }
  return sum;
}

// In most JavaScript engines, regularLoop will be much faster than sumWithGenerator

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.