What's the yield keyword in JavaScript?

Viewed 159144

I heard about a "yield" keyword in JavaScript, but I found very poor documentation about it. Can someone explain me (or recommend a site that explains) its usage and what it is used for?

14 Answers

The MDN documentation is pretty good, IMO.

The function containing the yield keyword is a generator. When you call it, its formal parameters are bound to actual arguments, but its body isn't actually evaluated. Instead, a generator-iterator is returned. Each call to the generator-iterator's next() method performs another pass through the iterative algorithm. Each step's value is the value specified by the yield keyword. Think of yield as the generator-iterator version of return, indicating the boundary between each iteration of the algorithm. Each time you call next(), the generator code resumes from the statement following the yield.

Yield keyword in javaScript function makes it generator,

What is generator in JavaScript?

A generator is a function that produces a sequence of results instead of a single value, i.e. you generate ​a series of values

Meaning generators helps us work asynchronously with the help iterators, Oh now what the hack iterators are? really?

Iterators are mean through which we are able to access items one at a time

From where iterator help us accessing item one at a time? It help us accessing items through generator functions, generator functions are those in which we use yield keyword, yield keyword help us in pausing and resuming execution of function.

Here is quick example:

function *getMeDrink() {

    let question1 = yield 'soda or beer'; // execution will pause here because of yield
       
    if (question1 == 'soda') {
        return 'here you get your soda';
    }

    if (question1 == 'beer') {

        let question2 = yield 'What\'s your age'; // execution will pause here because of yield

        if (question2 > 18) {
            return "ok you are eligible for it";
        } else {
            return "Shhhh!!!!";
        }
    }
}

let _getMeDrink = getMeDrink(); // initialize it

_getMeDrink.next().value; // "soda or beer"

_getMeDrink.next('beer').value; // "What's your age"

_getMeDrink.next('20').value; // "ok you are eligible for it"

_getMeDrink.next().value; // undefined

Let me briefly explain what is going on

You noticed execution is being paused at each yield keyword and we are able to access first yield with help of iterator .next()

This iterates to all yield keywords one at a time and then returns undefined when there is no more yield keywords left in simple words you can say yield keyword is break point where function each time pauses and only resume when call it using iterator for our case: _getMeDrink.next() this is example of iterator that is helping us accessing each break point in function.

Example of Generators: async/await

If you see implementation of async/await you will see generator functions & promises are used to make async/await work please point out any suggestions is welcomed.

It's used for iterator-generators. Basically, it allows you to make a (potentially infinite) sequence using procedural code. See Mozilla's documentation.

Dependency between async javascript calls.

Another good example of how yield can be used.

function request(url) {
  axios.get(url).then((reponse) => {
    it.next(response);
  })
}

function* main() {
  const result1 = yield request('http://some.api.com' );
  const result2 = yield request('http://some.otherapi?id=' + result1.id );
  console.log('Your response is: ' + result2.value);
}

var it = main();
it.next()

Before you learn about yield you need to know about generators. Generators are created using the function* syntax. Generator functions do not execute code but instead returns a type of iterator called a generator. When a value is given using the next method, the generator function keeps executing until it comes across a yield keyword. Using yield gives you back an object containing two values, one is value and the other is done (boolean). The value can be an array, object etc.

A simple example:

const strArr = ["red", "green", "blue", "black"];

const strGen = function*() {
    for(let str of strArr) {
        yield str;
    }
};

let gen = strGen();

for (let i = 0; i < 5; i++) {
    console.log(gen.next())
}

//prints: {value: "red", done: false} -> 5 times with different colors, if you try it again as below:

console.log(gen.next());

//prints: {value: undefined, done: true}

I am also trying to understand the yield keyword. Based my current understanding, in generator, yield keyword works like a CPU context-switch. When yield statement is run, all states (for example, local variables) are saved.

Besides this, a direct result object will be returned to the caller, like { value: 0, done: false }. The caller can use this result object to decide whether to 'wake up' the generator again by calling next() (calling next() is to iterate the execution).

Another important thing is that it can set a value to a local variable. This value can be passed by the 'next()' caller when 'waking up' the generator. for example, it.next('valueToPass'), like this: "resultValue = yield slowQuery(1);" Just like when waking up a next execution, caller can inject some running result to the execution (injecting it to local variable). Thus, for this execution, there are two kind of state:

  1. the context that saved in the last execution.

  2. The injected values by this execution's trigger.

So, with this feature, the generator can sort out multiple async operations. The result of first async query will be passed to the second one by setting local variable (resultValue in above example). The second async query can only be triggered by the first's async query's response. Then the second async query can check the local variable value to decide next steps because the local variable is an injected value from first query’s response.

The difficulties of async queries are:

  1. callback hell

  2. lose of context unless passing them as parameters in the callback.

yield and generator can help on both.

Without yield and generator, to sort out multiple async query requires nested callback with parameters as context which does not easy to read and maintain.

Below is a chained async queries example which running with nodejs:

const axios = require('axios');

function slowQuery(url) {        
    axios.get(url)
    .then(function (response) {
            it.next(1);
    })
    .catch(function (error) {
            it.next(0);
    })
}

function* myGen(i=0) {
    let queryResult = 0;

    console.log("query1", queryResult);
    queryResult = yield slowQuery('https://google.com');


    if(queryResult == 1) {
        console.log("query2", queryResult);
        //change it to the correct url and run again.
        queryResult = yield slowQuery('https://1111111111google.com');
    }

    if(queryResult == 1) {
        console.log("query3", queryResult);
        queryResult =  yield slowQuery('https://google.com');
    } else {
        console.log("query4", queryResult);
        queryResult = yield slowQuery('https://google.com');
    }
}

console.log("+++++++++++start+++++++++++");
let it = myGen();
let result = it.next();
console.log("+++++++++++end+++++++++++");

Below is the running result:

+++++++++++start+++++++++++

query1 0

+++++++++++end+++++++++++

query2 1

query4 0

Below state pattern can do the similar thing for above example:

const axios = require('axios');

function slowQuery(url) {
    axios.get(url)
        .then(function (response) {
            sm.next(1);
        })
        .catch(function (error) {
            sm.next(0);
        })
}

class StateMachine {
        constructor () {
            this.handler = handlerA;
            this.next = (result = 1) => this.handler(this, result);
        }
}

const handlerA = (sm, result) => {
                                    const queryResult = result; //similar with generator injection
                                    console.log("query1", queryResult);
                                    slowQuery('https://google.com');
                                    sm.handler = handlerB; //similar with yield;
                                };

const handlerB = (sm, result) => {
                                    const queryResult = result; //similar with generator injection
                                    if(queryResult == 1) {
                                        console.log("query2", queryResult);
                                        slowQuery('https://1111111111google.com');
                                    }
                                    sm.handler = handlerC; //similar with yield;
                                };

const handlerC = (sm, result) => {
                                    const queryResult = result; //similar with generator injection;
                                    if (result == 1 ) {
                                        console.log("query3", queryResult);
                                        slowQuery('https://google.com');
                                    } else {
                                        console.log("query4", queryResult);
                                        slowQuery('https://google.com');
                                    }
                                    sm.handler = handlerEnd; //similar with yield;
                                };

const handlerEnd = (sm, result) => {};

console.log("+++++++++++start+++++++++++");
const sm = new StateMachine();
sm.next();
console.log("+++++++++++end+++++++++++");

Following is the running result:

+++++++++++start+++++++++++

query1 0

+++++++++++end+++++++++++

query2 1

query4 0

don't forget the very helpful 'x of generator' syntax to loop through the generator. No need to use the next() function at all.

function* square(x){
    for(i=0;i<100;i++){
        x = x * 2;
        yield x;        
    }   
}

var gen = square(2);
for(x of gen){
   console.log(x);
}
Related