The yield keyword
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
上一篇:生成器函数语法
下一篇:<area>-图像映射区域