One place for hosting & domains

      JavaScript

      Understanding Default Parameters in JavaScript


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      In ECMAScript 2015, default function parameters were introduced to the JavaScript language. These allow developers to initialize a function with default values if the arguments are not supplied to the function call. Initializing function parameters in this way will make your functions easier to read and less error-prone, and will provide default behavior for your functions. This will help you avoid errors that stem from passing in undefined arguments and destructuring objects that don’t exist.

      In this article, you will review the difference between parameters and arguments, learn how to use default parameters in functions, see alternate ways to support default parameters, and learn what types of values and expressions can be used as default parameters. You will also run through examples that demonstrate how default parameters work in JavaScript.

      Arguments and Parameters

      Before explaining default function parameters, it is important to know what it is that parameters can default to. Because of this, we will first review the difference between arguments and parameters in a function. If you would like to learn more about this distinction, check out our earlier article in the JavaScript series, How to Define Functions in JavaScript.

      In the following code block, you will create a function that returns the cube of a given number, defined as x:

      // Define a function to cube a number
      function cube(x) {
        return x * x * x
      }
      

      The x variable in this example is a parameter—a named variable passed into a function. A parameter must always be contained in a variable and must never have a direct value.

      Now take a look at this next code block, which calls the cube function you just created:

      // Invoke cube function
      cube(10)
      

      This will give the following output:

      Output

      1000

      In this case, 10 is an argument—a value passed to a function when it is invoked. Often the value will be contained in a variable as well, such as in this next example:

      // Assign a number to a variable
      const number = 10
      
      // Invoke cube function
      cube(number)
      

      This will yield the same result:

      Output

      1000

      If you do not pass an argument to a function that expects one, the function will implicitly use undefined as the value:

      // Invoke the cube function without passing an argument
      cube()
      

      This will return:

      Output

      NaN

      In this case, cube() is trying to calculate the value of undefined * undefined * undefined, which results in NaN, or “not a number”. For more on this, take a look at the number section of Understanding Data Types in JavaScript.

      This automatic behavior can sometimes be a problem. In some cases, you might want the parameter to have a value even if no argument was passed to the function. That’s where the default parameters feature comes in handy, a topic that you will cover in the next section.

      Default Parameter Syntax

      With the addition of default parameters in ES2015, you can now assign a default value to any parameter, which the function will use instead of undefined when called without an argument. This section will first show you how to do this manually, and then will guide you through setting default parameters.

      Without default parameters, you would have to explicitly check for undefined values in order to set defaults, as is shown in this example:

      // Check for undefined manually
      function cube(x) {
        if (typeof x === 'undefined') {
          x = 5
        }
      
        return x * x * x
      }
      
      cube()
      

      This uses a conditional statement to check if the value has been automatically provided as undefined, then sets the value of x as 5. This will result in the following output:

      Output

      125

      In contrast, using default parameters accomplishes the same goal in much less code. You can set a default value to the parameter in cube by assigning it with the equality assignment operator (=), as highlighted here:

      // Define a cube function with a default value
      function cube(x = 5) {
        return x * x * x
      }
      

      Now when the cube function is invoked without an argument, it will assign 5 to x and return the calculation instead of NaN:

      // Invoke cube function without an argument
      cube()
      

      Output

      125

      It will still function as intended when an argument is passed, ignoring the default value:

      // Invoke cube function with an argument
      cube(2)
      

      Output

      8

      However, one important caveat to note is that the default parameter value will also override an explicit undefined passed as an argument to a function, as demonstrated here:

      // Invoke cube function with undefined
      cube(undefined)
      

      This will give the calculation with x equal to 5:

      Output

      125

      In this case, the default parameter values were calculated, and an explicit undefined value did not override them.

      Now that you have an idea of the basic syntax of default parameters, the next section will show how default parameters work with different data types.

      Default Parameter Data Types

      Any primitive value or object can be used as a default parameter value. In this section, you will see how this flexibility increases the ways in which default parameters can be used.

      First, set parameters using a number, string, boolean, object, array, and null value as a default value. This example will use arrow function syntax:

      // Create functions with a default value for each data type
      const defaultNumber = (number = 42) => console.log(number)
      const defaultString = (string = 'Shark') => console.log(string)
      const defaultBoolean = (boolean = true) => console.log(boolean)
      const defaultObject = (object = { id: 7 }) => console.log(object)
      const defaultArray = (array = [1, 2, 3]) => console.log(array)
      const defaultNull = (nullValue = null) => console.log(nullValue)
      

      When these functions are invoked without parameters, they will all use the default values:

      // Invoke each function
      defaultNumber()
      defaultString()
      defaultBoolean()
      defaultObject()
      defaultArray()
      defaultNull()
      

      Output

      42 "Shark" true {id: 7} (3) [1, 2, 3] null

      Note that any object created in a default parameter will be created every time the function is called. One of the common use cases for default parameters is to use this behavior to obtain values out of an object. If you try to destructure or access a value from an object that doesn’t exist, it will throw an error. However, if the default parameter is an empty object, it will simply give you undefined values instead of throwing an error:

      // Define a settings function with a default object
      function settings(options = {}) {
        const { theme, debug } = options
      
        // Do something with settings
      }
      

      This will avoid the error caused by destructuring objects that don’t exist.

      Now that you’ve seen how default parameters operate with different data types, the next section will explain how multiple default parameters can work together.

      Using Multiple Default Parameters

      You can use as many default parameters as you want in a function. This section will show you how to do this, and how to use it to manipulate the DOM in a real-world example.

      First, declare a sum() function with multiple default parameters:

      // Define a function to add two values
      function sum(a = 1, b = 2) {
        return a + b
      }
      
      sum()
      

      This will result in the following default calculation:

      Output

      3

      Additionally, the value used in a parameter can be used in any subsequent default parameter, from left to right. For example, this createUser function creates a user object userObj as the third parameter, and all the function itself does is return userObj with the first two parameters:

      // Define a function to create a user object using parameters
      function createUser(name, rank, userObj = { name, rank }) {
        return userObj
      }
      
      // Create user
      const user = createUser('Jean-Luc Picard', 'Captain')
      

      If you call user here, you will get the following:

      Output

      {name: "Jean-Luc Picard", rank: "Captain"}

      It is usually recommended to put all default parameters at the end of a list of parameters, so that you can easily leave off optional values. If you use a default parameter first, you will have to explicitly pass undefined to use the default value.

      Here is an example with the default parameter at the beginning of the list:

      // Define a function with a default parameter at the start of the list
      function defaultFirst(a = 1, b) {
        return a + b
      }
      

      When calling this function, you would have to call defaultFirst() with two arguments:

      defaultFirst(undefined, 2)
      

      This would give the following:

      Output

      3

      Here is an example with the default parameter at the end of the list:

      // Define a function with a default parameter at the end of the list
      function defaultLast(a, b = 1) {
        return a + b
      }
      
      defaultLast(2)
      

      This would yield the same value:

      Output

      3

      Both functions have the same result, but the one with the default value last allows a much cleaner function call.

      For a real-world example, here is a function that will create a DOM element, and add a text label and classes, if they exist.

      // Define function to create an element
      function createNewElement(tag, text, classNames = []) {
        const el = document.createElement(tag)
        el.textContent = text
      
        classNames.forEach(className => {
          el.classList.add(className)
        })
      
        return el
      }
      

      You can call the function with some classes in an array:

      const greeting = createNewElement('p', 'Hello!', ['greeting', 'active'])
      

      Calling greeting will give the following value:

      Output

      <p class="greeting active">Hello!</p>

      However, if you leave the classNames array out of the function call, it will still work.

      const greeting2 = createNewElement('p', 'Hello!')
      

      greeting2 now has the following value:

      Output

      <p>Hello!</p>

      In this example, forEach() can be used on an empty array without an issue. If that empty array were not set in the default parameter, you would get the following error:

      Output

      VM2673:5 Uncaught TypeError: Cannot read property 'forEach' of undefined at createNewElement (<anonymous>:5:14) at <anonymous>:12:18

      Now that you have seen how multiple default parameters can interact, you can move on to the next section to see how function calls work as default parameters.

      Function Calls as Default Parameters

      In addition to primitives and objects, the result of calling a function can be used as a default parameter.

      In this code block, you will create a function to return a random number, and then use the result as the default parameter value in a cube function:

      // Define a function to return a random number from 1 to 10
      function getRandomNumber() {
        return Math.floor(Math.random() * 10)
      }
      
      // Use the random number function as a default parameter for the cube function
      function cube(x = getRandomNumber()) {
        return x * x * x
      }
      

      Now invoking the cube function without a parameter will have potentially different results every time you call it:

      // Invoke cube function twice for two potentially different results
      cube()
      cube()
      

      The output from these function calls will vary:

      Output

      512 64

      You can even use built-in methods, like those on the Math object, and use the value returned in one function call as a parameter in another function.

      In the following example, a random number is assigned to x, which is used as the parameter in the cube function you created. The y parameter will then calculate the cube root of the number and check to see if x and y are equal:

      // Assign a random number to x
      // Assign the cube root of the result of the cube function and x to y
      function doesXEqualY(x = getRandomNumber(), y = Math.cbrt(cube(x))) {
        return x === y
      }
      
      doesXEqualY()
      

      This will give the following:

      Output

      true

      A default parameter can even be a function definition, as seen in this example, which defines a parameter as the inner function and returns the function call of parameter:

      // Define a function with a default parameter that is an anonymous function
      function outer(
        parameter = function inner() {
          return 100
        }
      ) {
        return parameter()
      }
      
      // Invoke outer function
      outer()
      

      Output

      100

      This inner function will be created from scratch every time the outer function is invoked.

      Conclusion

      In this article, you learned what default function parameters are and how to use them. Now you can use default parameters to help keep your functions clean and easy to read. You can also assign empty objects and arrays to parameters upfront to reduce both complexity and lines of code when dealing with situations such as retrieving values from an object or looping through an array.

      If you would like to learn more about JavaScript, check out the homepage for our How To Code in JavaScript series, or browse our How to Code in Node.js series for articles on back-end development.



      Source link

      Información sobre generadores en JavaScript


      El autor seleccionó a Open Internet/Free Speech Fund para recibir una donación como parte del programa Write for DOnations.

      Introducción

      En ECMAScript 2015, se introdujeron generadores en el lenguaje de JavaScript. Un generador es un proceso que puede pausarse, reanudarse y producir varios valores. Un generador en JavaScript consta de una función generadora que muestra un objeto iterable Generator.

      Los generadores pueden mantener el estado, y proporcionar con ello una forma eficiente de crear iteradores, y encargarse de flujos de datos infinitos que se pueden emplear para implementar desplazamiento infinito en una página en el frontend de una aplicación web, para operar con datos de ondas de sonido, y más. Además, cuando se usan con promesas, los generadores pueden imitar la funcionalidad de async/await, lo que nos permite abordar el código asíncrono de una manera más sencilla y legible. Aunque async/await es una alternativa más frecuente para abordar los casos de uso asíncrono más comunes y sencillos, como la obtención de datos de una API, en los generadores se incluyen funciones más avanzadas que hacen que valga la pena aprender a usarlos.

      En este artículo, veremos la forma de crear funciones generadoras e iterar objetos Generator, la diferencia entre yield y return dentro de un generador y otros aspectos vinculados al trabajo con generadores.

      Funciones generadoras

      Una función generadora es una función que muestra un objeto Generator y se define con la palabra clave function seguida de un asterisco (*), como se muestra a continuación:

      // Generator function declaration
      function* generatorFunction() {}
      

      De vez en cuando, verá el asterisco junto al nombre de la función, a diferencia de la palabra clave de la función; por ejemplo, function *generatorFunction(). Esto funciona igual, aunque la de function* es una sintaxis más aceptada.

      Las funciones generadoras también pueden definirse en una expresión, como las funciones regulares:

      // Generator function expression
      const generatorFunction = function*() {}
      

      Los generadores pueden ser, incluso, los métodos de un objeto o una clase:

      // Generator as the method of an object
      const generatorObj = {
        *generatorMethod() {},
      }
      
      // Generator as the method of a class
      class GeneratorClass {
        *generatorMethod() {}
      }
      

      En los ejemplos de este artículo se usará la sintaxis de declaración de las funciones generadoras.

      Nota: A diferencia de las funciones regulares, los generadores no se pueden construir a partir de la palabra clave new ni pueden ser utilizarse junto con las funciones de flecha.

      Ahora que sabe declarar funciones generadoras, veremos los objetos Generator iterables que estas muestran.

      Objetos Generator

      Tradicionalmente, las funciones en JavaScript se ejecutan hasta completarse y la invocación de una muestra un valor cuando se alcanza la palabra clave return. Si se omite la palabra clave return, en una función se mostrará implícitamente undefined.

      En el siguiente código, por ejemplo, declaramos una función sum() que muestra un valor que es la suma de dos argumentos enteros:

      // A regular function that sums two values
      function sum(a, b) {
        return a + b
      }
      

      Al invocar la función se muestra un valor que es la suma de los argumentos:

      const value = sum(5, 6) // 11
      

      Sin embargo, una función generadora no muestra un valor de inmediato y como alternativa se muestra un objeto Generator iterable. En el siguiente ejemplo, se declara una función y se le asigna un valor de devolución único, como en una función estándar:

      // Declare a generator function with a single return value
      function* generatorFunction() {
        return 'Hello, Generator!'
      }
      

      Cuando invoquemos la función generadora, mostrará el objeto Generator, que podemos asignar a una variable:

      // Assign the Generator object to generator
      const generator = generatorFunction()
      

      Si esta fuera una función regular, esperaríamos que generator nos proporcionara la cadena mostrada en la función. Sin embargo, lo que realmente obtenemos es un objeto en estado suspended. Al invocar generator, por lo tanto, obtendremos un resultado similar al siguiente:

      Output

      generatorFunction {<suspended>} __proto__: Generator [[GeneratorLocation]]: VM272:1 [[GeneratorStatus]]: "suspended" [[GeneratorFunction]]: ƒ* generatorFunction() [[GeneratorReceiver]]: Window [[Scopes]]: Scopes[3]

      El objeto Generator mostrado por la función es un iterador. Un iterador es un objeto que tiene un método next() disponible, el cual se utiliza para iterar una secuencia de valores. El método next() muestra un objeto con propiedades value y done. value representa el valor mostrado y done indica si el iterador recorrió o no todos sus valores.

      Conociendo esto, invocaremos next() en nuestro generator y obtendremos el valor y el estado actual del iterador:

      // Call the next method on the Generator object
      generator.next()
      

      Esto generará el siguiente resultado:

      Output

      {value: "Hello, Generator!", done: true}

      El valor mostrado después de invocar next() es Hello, Generator!, y el estado de done es true, ya que este valor provino de un return que cerró el iterador. Debido a que la operación del iterador se completó, el estado de la función generadora pasará de suspended a closed. Invocar generator de nuevo dará el siguiente resultado:

      Output

      generatorFunction {<closed>}

      Hasta ahora, solo hemos demostrado que una función generadora puede ser una alternativa más compleja para obtener el valor return de una función. Pero las funciones generadoras también tienen características únicas que las distinguen de las funciones normales. En la siguiente sección, aprenderá sobre el operador yield y veremos cómo se puede pausar y reanudar la ejecución de un generador.

      Operadores yield

      Con los generadores se introdujo una nueva palabra clave en JavaScript: yield. Con yield se puede pausar una función generadora y mostrar el valor que le sigue a yield, y así proporcionar una opción ligera para iterar valores.

      En este ejemplo, pausaremos la función generadora tres veces con diferentes valores y mostraremos un valor al final. Luego asignaremos nuestro objeto Generator a la variable generator.

      // Create a generator function with multiple yields
      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      
        return 'The Oracle'
      }
      
      const generator = generatorFunction()
      

      Ahora, cuando invoquemos next() en la función generadora, se pausará cada vez que encuentre yield. Se fijará el valor false para done después de cada yield, lo cual indicará que el generador no ha terminado. Una vez que encuentre un return, o ya no se encuentren más yield en la función, done pasará a tener el valor true y el generador habrá terminado.

      Utilice el método next() cuatro veces seguidas:

      // Call next four times
      generator.next()
      generator.next()
      generator.next()
      generator.next()
      

      Estos darán las siguientes cuatro líneas de resultados en orden:

      Output

      {value: "Neo", done: false} {value: "Morpheus", done: false} {value: "Trinity", done: false} {value: "The Oracle", done: true}

      Tenga en cuenta que un generador no requiere un return; si se omite, la última iteración mostrará {value: undefined, done: true}, al igual que cualquier invocación posterior de next() después de que un generador haya finalizado.

      Iterar un generador

      Usando el método next(), iteramos manualmente el objeto Generator y recibimos todas las propiedades de value y done del objeto completo. Sin embargo, al igual que Array, Map y Set, un Generator sigue el protocolo de iteración y puede iterarse con for...of:

      // Iterate over Generator object
      for (const value of generator) {
        console.log(value)
      }
      

      Con esto, se mostrará lo siguiente:

      Output

      Neo Morpheus Trinity

      El operador de propagación también puede usarse para asignar los valores de un Generator en una matriz.

      // Create an array from the values of a Generator object
      const values = [...generator]
      
      console.log(values)
      

      Esto proporcionará la siguiente matriz:

      Output

      (3) ["Neo", "Morpheus", "Trinity"]

      Tanto con la propagación como con for...of, no se tomará en cuenta return en los valores (en este caso, habría sido 'The Oracle').

      Nota: Si bien ambos métodos son eficaces para trabajar con generadores finitos, si un generador se encarga de un flujo de datos infinito, no será posible usar directamente la propagación ni for...of sin crear un bucle infinito.

      Cerrar un generador

      Como vimos, en el caso de un generador se puede fijar el valor true para la propiedad done y el valor closed para su estado mediante la iteración de todos sus valores. Existen dos alternativas adicionales para cancelar inmediatamente un generador: el método return() y el método throw().

      Con return(), el proceso del generador se puede finalizar en cualquier punto, como si hubiera una declaración return en el cuerpo de la función. Puede pasar un argumento a return() o dejarlo en blanco para un valor indefinido.

      Para demostrar return(), se creará un generador con algunos valores yield pero sin return en la definición de la función:

      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      }
      
      const generator = generatorFunction()
      

      El primer next() nos proporcionará 'Neo', con el valor false fijado para done. Si invocamos un método return() en el objeto Generator justo después de esto, obtendremos el valor pasado y se cambiará done a true. Cualquier invocación adicional de next() nos proporcionará la respuesta del generador completada predeterminada con un valor indefinido.

      Para demostrar esto, ejecute los siguientes tres métodos en generator:

      generator.next()
      generator.return('There is no spoon!')
      generator.next()
      

      Esto proporcionará los tres resultados siguientes:

      Output

      {value: "Neo", done: false} {value: "There is no spoon!", done: true} {value: undefined, done: true}

      El método return() forzó al objeto Generator a completarse e ignorar cualquier otra palabra clave de yield. Esto es particularmente útil en la programación asíncrona cuando es necesario hacer que las funciones puedan cancelarse; por ejemplo, mediante la interrupción de una solicitud web cuando un usuario quiere realizar una acción diferente, ya que no es posible cancelar directamente una promesa.

      Si en el cuerpo de una función generadora se incluye una alternativa para detectar y manejar los errores, puede usar el método throw() para generar un error en el generador. Con esto se inicia el generador, se genera un error y se finaliza el generador.

      Para demostrar esto, dispondremos un try...catch dentro del cuerpo de la función generadora y registraremos un error si se encuentra:

      // Define a generator function with a try...catch
      function* generatorFunction() {
        try {
          yield 'Neo'
          yield 'Morpheus'
        } catch (error) {
          console.log(error)
        }
      }
      
      // Invoke the generator and throw an error
      const generator = generatorFunction()
      

      Ahora, ejecutaremos el método next(), seguido de throw():

      generator.next()
      generator.throw(new Error('Agent Smith!'))
      

      Esto generará el siguiente resultado:

      Output

      {value: "Neo", done: false} Error: Agent Smith! {value: undefined, done: true}

      Mediante throw(), se introdujo en el generador un error detectado por try...catch y registrado en la consola.

      Métodos y estados del objeto generador

      En la siguiente tabla, se muestra una lista de métodos que pueden utilizarse en los objetos Generator:

      Método Descripción
      next() Muestra el valor siguiente en un generador.
      return() Muestra un valor en un generador y finaliza el generador.
      throw() Genera un error y finaliza el generador.

      En la siguiente tabla, se enumeran los posibles estados de un objeto Generator:

      Estado Descripción
      suspended La ejecución del generador se detuvo, pero el proceso de este no finalizó.
      closed El proceso del generador finalizó porque se produjo un error, un retorno o una iteración de todos los valores.

      Delegación de yield

      Además del operador regular yield, los generadores también pueden usar la expresión yield* para delegar más valores a otros generadores. Cuando se encuentre yield* dentro de un generador, se ubicará dentro del generador delegado y empezarán a iterarse todos los yield hasta que se cierre ese generador. Esto se puede utilizar para separar diferentes funciones generadoras con el fin de organizar su código de forma semántica y, al mismo tiempo, iterar todos los yield en el orden correcto.

      Para demostrar esto, podemos crear dos funciones generadoras, una de las cuales operará mediante yield* en la otra:

      // Generator function that will be delegated to
      function* delegate() {
        yield 3
        yield 4
      }
      
      // Outer generator function
      function* begin() {
        yield 1
        yield 2
        yield* delegate()
      }
      

      A continuación, iteraremos la función generadora begin():

      // Iterate through the outer generator
      const generator = begin()
      
      for (const value of generator) {
        console.log(value)
      }
      

      Esto dará los siguientes valores en el orden en que se generan:

      Output

      1 2 3 4

      El generador externo produjo los valores 1 y 2, y luego se delegó al otro generador con yield*, que mostró 3 y 4.

      yield* también puede hacer delegaciones a cualquier objeto que sea iterable, como una matriz o un mapa. La delegación de Yield puede ser útil para organizar código, ya que cualquier función de un generador que intente usar yield tendría que ser también un generador.

      Flujos de datos infinitos

      Uno de los aspectos útiles de los generadores es la capacidad de trabajar con flujos de datos infinitos y grupos. Esto puede demostrarse creando un bucle infinito dentro de una función generadora que incremente un número en una unidad.

      En el siguiente bloque de código, definimos esta función generadora y luego iniciamos el generador:

      // Define a generator function that increments by one
      function* incrementer() {
        let i = 0
      
        while (true) {
          yield i++
        }
      }
      
      // Initiate the generator
      const counter = incrementer()
      

      Ahora, itere los valores usando next():

      // Iterate through the values
      counter.next()
      counter.next()
      counter.next()
      counter.next()
      

      Esto generará el siguiente resultado:

      Output

      {value: 0, done: false} {value: 1, done: false} {value: 2, done: false} {value: 3, done: false}

      En la función se muestran los valores sucesivos en el bucle infinito mientras que el valor de la propiedad done se mantiene en false, lo cual garantiza que no finalizará.

      Con los generadores, no tiene que preocuparse por crear un bucle infinito, porque puede detener y reanudar la ejecución cuando lo desee. Sin embargo, de todos modos debe tener cuidado con la forma en la que invoca el generador. Si utiliza la propagación o for...of en un flujo de datos infinito, seguirá iterando un bucle infinito todo a la vez, lo cual hará que el entorno se bloquee.

      Para un ejemplo más complejo de un flujo de datos infinito, podemos crear una función generadora de Fibonacci. La secuencia de Fibonacci, que suma continuamente los dos valores anteriores, puede escribirse usando un bucle infinito en un generador, como se muestra a continuación:

      // Create a fibonacci generator function
      function* fibonacci() {
        let prev = 0
        let next = 1
      
        yield prev
        yield next
      
        // Add previous and next values and yield them forever
        while (true) {
          const newVal = next + prev
      
          yield newVal
      
          prev = next
          next = newVal
        }
      }
      

      Para probar esto, podemos usar un bucle un número finito de veces e imprimir la secuencia de Fibonacci en la consola.

      // Print the first 10 values of fibonacci
      const fib = fibonacci()
      
      for (let i = 0; i < 10; i++) {
        console.log(fib.next().value)
      }
      

      Esto dará el siguiente resultado:

      Output

      0 1 1 2 3 5 8 13 21 34

      La capacidad de trabajar con conjuntos de datos infinitos es una de las razones por la cuales los generadores son tan poderosos. Esto puede ser útil para ejemplos como el caso de la implementación de desplazamiento infinito en el frontend de una aplicación web.

      Pasar los valores en los generadores

      A lo largo de este artículo, usamos generadores como iteradores y produjimos valores en cada iteración. Además de producir valores, los generadores también pueden consumir valores de next(). En este caso, yield contendrá un valor.

      Es importante observar que con el primer next() que se invoqué no se pasará un valor, sino que solo se iniciará el generador. Para demostrar esto, podemos registrar el valor de yield e invocar next() unas cuantas veces con algunos valores.

      function* generatorFunction() {
        console.log(yield)
        console.log(yield)
      
        return 'The end'
      }
      
      const generator = generatorFunction()
      
      generator.next()
      generator.next(100)
      generator.next(200)
      

      Esto generará el siguiente resultado:

      Output

      100 200 {value: "The end", done: true}

      También es posible propagar el generador con un valor inicial. En el siguiente ejemplo, crearemos un bucle for y pasaremos cada valor al método next(), pero también pasaremos un argumento a la función inicial:

      function* generatorFunction(value) {
        while (true) {
          value = yield value * 10
        }
      }
      
      // Initiate a generator and seed it with an initial value
      const generator = generatorFunction(0)
      
      for (let i = 0; i < 5; i++) {
        console.log(generator.next(i).value)
      }
      

      Recuperaremos el valor de next() y produciremos un nuevo valor para la siguiente iteración, que es igual a diez veces el valor anterior. Esto dará el siguiente resultado:

      Output

      0 10 20 30 40

      Otra forma de abordar la iniciación de un generador es ajustarlo en una función que siempre invocará next() una vez antes de hacer cualquier otra acción.

      async y await con generadores

      Una función asíncrona es un tipo de función disponible en ES6+ de JavaScript que facilita el trabajo con datos asíncronos al hacer que parezcan síncronos. Los generadores tienen una matriz de capacidades más amplia que las funciones asíncronas, pero son capaces de replicar un comportamiento similar. Implementar de esta manera una programación asíncrona puede aumentar la flexibilidad de su código.

      En esta sección, mostraremos un ejemplo de la reproducción de async y await con generadores.

      Crearemos una función asíncrona que utiliza la API de Fetch para obtener datos de la API JSONPlaceholder (que proporciona datos JSON de ejemplo para pruebas) y registrar la respuesta en la consola.

      Comience definiendo una función asíncrona llamada getUsers, que obtiene datos de la API y muestra una matriz de objetos, y luego invoque getUsers:

      const getUsers = async function() {
        const response = await fetch('https://jsonplaceholder.typicode.com/users')
        const json = await response.json()
      
        return json
      }
      
      // Call the getUsers function and log the response
      getUsers().then(response => console.log(response))
      

      Esto proporcionará datos JSON similares a los siguientes:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Usando generadores, podemos crear algo casi idéntico que no utilice las palabras claves async y await. En su lugar, se usarán una nueva función que creamos y los valores yield en vez de las promesas await.

      En el siguiente bloque de código, definimos una función llamada getUsers que utiliza nuestra nueva función asyncAlt (que escribiremos más adelante) para imitar a async y await.

      const getUsers = asyncAlt(function*() {
        const response = yield fetch('https://jsonplaceholder.typicode.com/users')
        const json = yield response.json()
      
        return json
      })
      
      // Invoking the function
      getUsers().then(response => console.log(response))
      

      Como podemos ver, es casi idéntica a la implementación de async y await, excepto que se pasa una función generadora que produce valores.

      Ahora podemos crear una función asyncAlt que se asemeje a una función asíncrona. asyncAlt cuenta con una función generadora como un parámetro, que es nuestra función productora de promesas que muestra fetch. asyncAlt muestra una función por sí misma y resuelve cada promesa que encuentra hasta la última:

      // Define a function named asyncAlt that takes a generator function as an argument
      function asyncAlt(generatorFunction) {
        // Return a function
        return function() {
          // Create and assign the generator object
          const generator = generatorFunction()
      
          // Define a function that accepts the next iteration of the generator
          function resolve(next) {
            // If the generator is closed and there are no more values to yield,
            // resolve the last value
            if (next.done) {
              return Promise.resolve(next.value)
            }
      
            // If there are still values to yield, they are promises and
            // must be resolved.
            return Promise.resolve(next.value).then(response => {
              return resolve(generator.next(response))
            })
          }
      
          // Begin resolving promises
          return resolve(generator.next())
        }
      }
      

      Esto dará el mismo resultado que la versión de async y await:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Tenga en cuenta que esta implementación es para demostrar cómo se pueden usar los generadores en lugar de async y await, y que no es un diseño listo para la producción. No tiene configurada la gestión de errores ni tiene la capacidad de pasar los parámetros a los valores producidos. Aunque con este método se puede añadir flexibilidad a su código, a menudo async/await será una mejor opción, ya que abstrae los detalles de la implementación y le permite enfocarse en escribir código productivo.

      Conclusión

      Los generadores son procesos que cuya ejecución puede detenerse y reanudarse. Son una potente y versátil característica de JavaScript, aunque no se utilizan comúnmente. En este tutorial, aprendió acerca de las funciones generadoras y los objetos generadores, los métodos disponibles para los generadores, los operadores yield y yield* y los generadores utilizados con conjuntos de datos finitos e infinitos. También exploramos una manera de implementar código asíncrono sin devoluciones de llamada anidadas ni largas cadenas de promesas.

      Si desea aprender más sobre la sintaxis de JavaScript, consulte nuestros tutoriales Información sobre This, Bind, Call y Apply en JavaScript e Información sobre los objetos Map y Set en JavaScript.



      Source link

      Понимание генераторов в JavaScript


      Автор выбрал фонд Open Internet/Free Speech для получения пожертвования в рамках программы Write for DOnations.

      Введение

      В ECMAScript 2015 были введены генераторы для языка JavaScript. Генератор — это процесс, который может быть остановлен и возобновлен, и может выдать несколько значений. Генаратор в JavaScript состоит из функции генераторов, которая возвращает элемент Generator, поддерживающий итерации.

      Генераторы могут поддерживать состояние и обеспечивать эффективный способ создания итераторов, а также позволяют работать с бесконечным потоком данных, который можно использовать для установки бесконечной прокрутки на внешнем интерфейсе веб-приложений, для работы с данными звуковой волны и т. д. Кроме того, при использовании Promises генераторы могут имитировать функцию async/await, которая позволяет работать с асинхронным кодом более простым и читаемым способом. Хотя async/await является более распространенным способом работы с асинхронными вариантами использования, например извлечения данных из API, генераторы обладают более усовершенствованными функциями, что абсолютно оправдывает изучение методов их использования.

      В этой статье мы расскажем, как создавать функции-генераторы, выполнять итеративный обход объектов Generator, объясним разницу между yield и return внутри генератора, а также коснемся других аспектов работы с генераторами.

      Функции-генераторы

      Функция-генератор — это функция, которая возвращает объект генератора и определяется по ключевому слову функции, за которым следует звездочка (*), как показано ниже:

      // Generator function declaration
      function* generatorFunction() {}
      

      Иногда звездочка отображается рядом с названием функции напротив ключевого слова, например function *generatorFunction(). Это работает так же, но функция со звездочкой function* является более распространенной синтаксической конструкцией.

      Функции-генераторы также могут определяться в выражении, как обычные функции:

      // Generator function expression
      const generatorFunction = function*() {}
      

      Генераторы могут даже быть методами объекта или класса:

      // Generator as the method of an object
      const generatorObj = {
        *generatorMethod() {},
      }
      
      // Generator as the method of a class
      class GeneratorClass {
        *generatorMethod() {}
      }
      

      В примерах, приведенных в данной статье, будет использоваться синтаксическая конструкция объявления функции генератора.

      Примечание. В отличие от обычных функций, генераторы не могут быть построены с помощью нового ключевого слова и не могут использоваться в сочетании со стрелочными функциями.

      Теперь, когда вы знаете, как объявлять функции-генераторы, давайте рассмотрим итерируемые объекты генератора, которые они возвращают.

      Объекты генератора

      Обычно функции в JavaScript выполняются до завершения, и вызов функции вернет значение, когда она дойдет до ключевого слова return. Если пропущено ключевое слово ​​​return, функция вернет значение undefined.

      Например, в следующем коде мы декларируем функцию sum(), которая возвращает значение, состоящее из суммы двух целых аргументов:

      // A regular function that sums two values
      function sum(a, b) {
        return a + b
      }
      

      Вызов функции возвращает значение, которое представляет собой сумму аргументов:

      const value = sum(5, 6) // 11
      

      Однако функция генератора не возвращает значение сразу, а вместо этого возвращает элемент Generator, поддерживающий итерации. В следующем примере мы декларируем функцию и придаем ей одно возвращаемое значение, как у стандартной функции:

      // Declare a generator function with a single return value
      function* generatorFunction() {
        return 'Hello, Generator!'
      }
      

      Активация функции генератора возвращает элемент Generator, который мы можем отнести к переменной:

      // Assign the Generator object to generator
      const generator = generatorFunction()
      

      Если бы это была штатная функция, мы бы могли ожидать, что генератор даст нам строку, переданную в функцию. Однако фактически мы получаем элемент в приостановленном состоянии. Таким образом, вызов генератора даст результат, аналогичный следующему:

      Output

      generatorFunction {<suspended>} __proto__: Generator [[GeneratorLocation]]: VM272:1 [[GeneratorStatus]]: "suspended" [[GeneratorFunction]]: ƒ* generatorFunction() [[GeneratorReceiver]]: Window [[Scopes]]: Scopes[3]

      Элемент Generator, возвращаемый функцией — это итератор. Итератор — это объект, имеющий метод ​​​​​​next()​​​, который используется для итерации последовательности значений. Метод next() возвращает элемент со свойствами value и done. value означает возвращаемое значение, а done указывает, прошел ли итератор все свои значения или нет.

      Зная это, давайте вызовем функцию next() нашего генератора и получим текущее значение и состояние итератора:

      // Call the next method on the Generator object
      generator.next()
      

      Результат будет выглядеть следующим образом:

      Output

      {value: "Hello, Generator!", done: true}

      Вызов next() возвращает значение Hello, Generator!, а состояние done имеет значение true, так как это значение произошло из return, что закрыло итератор. Поскольку итератор выполнен, статус функции генератора будет изменен с suspended на closed. Повторный вызов генератора даст следующее:

      Output

      generatorFunction {<closed>}

      На данный момент мы лишь продемонстрировали, как с помощью функции генератора более сложным способом можно получить значение функции return. Однако функции генератора также имеют уникальные свойства, которые отличают их от обычных функций. В следующем разделе мы узнаем об операторе yield и о том, как генератор может приостановить или возобновить выполнение.

      Операторы yield

      Генераторы вводят новое ключевое слово в JavaScript: yield. yield может приостановить функцию генератора и вернуть значение, которое следует за yield, тем самым обеспечивая более простой способ итерации значений.

      В этом примере мы остановим функцию генератора три раза с помощью разных значений и вернем значение в конце. Затем мы назначим наш объект Generator для переменной генератора.

      // Create a generator function with multiple yields
      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      
        return 'The Oracle'
      }
      
      const generator = generatorFunction()
      

      Сейчас, когда мы вызываем next()​​​​​ в функции генератора, она будет останавливаться каждый раз, когда будет встречать yield. done будет устанавливаться для false​​​ после каждого yield, указывая на то, что генератор не завершен. Когда она встретит return или в функции больше не будет yield, done переключится на true, и генератор будет завершен.

      Используйте метод next() четыре раза в строке:

      // Call next four times
      generator.next()
      generator.next()
      generator.next()
      generator.next()
      

      В результате будут выведены следующие четыре строки по порядку:

      Output

      {value: "Neo", done: false} {value: "Morpheus", done: false} {value: "Trinity", done: false} {value: "The Oracle", done: true}

      Обратите внимание, что для генератора не требуется return. В случае пропуска последняя итерация вернет {value: undefined, done: true}​​​, по мере наличия последующих вызовов next() после завершения генератора.

      Итерация по генератору

      С помощью метода next() мы вручную выполнили итерацию объекта Generator​​​, получив все свойства value​​​ и done всего объекта. Однако, как и Array,Map и Set, Generator следует протоколу итерации и может быть итерирован с for...of:

      // Iterate over Generator object
      for (const value of generator) {
        console.log(value)
      }
      

      В результате будет получено следующее:

      Output

      Neo Morpheus Trinity

      Оператор расширения также может быть использован для присвоения значений Generator​​​ для массива.

      // Create an array from the values of a Generator object
      const values = [...generator]
      
      console.log(values)
      

      Это даст следующий массив:

      Output

      (3) ["Neo", "Morpheus", "Trinity"]

      Как расширение, так и for...of​​​ не разложит return на значения (в этом случае было бы «The Oracle»).

      Примечание. Хотя оба эти метода эффективны для работы с конечными генераторами, если генератор работает с бесконечным потоком данных, невозможно будет использовать расширение или for...of​​​ напрямую без создания бесконечного цикла.

      Завершение работы генератора

      Как мы увидели, генератор может настроить свое свойство done​​​ на true, а статус на closed путем итерации всех своих значений. Немедленно отменить действие генератора можно еще двумя способами: с помощью метода return() и метода throw().

      С помощью return()​​ генератор можно остановить на любом этапе так, как будто выражение return было в теле функции. Вы можете передать аргумент в return() или оставить его пустым для неопределенного значения.

      Чтобы продемонстрировать return(), мы создадим генератор с несколькими значениями yield, но без return в определении функции:

      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      }
      
      const generator = generatorFunction()
      

      Первый next() даст нам «Neo» c done установленным на false​​​. Если мы обратимся к методу return()​​​ на объекте Generator сразу после этого, мы получим переданное значение, и done будет установлено на true. Все дополнительные вызовы next() дадут завершенный ответ генератора по умолчанию с неопределенным значением.

      Чтобы продемонстрировать это, запустите следующие три метода на генераторе:

      generator.next()
      generator.return('There is no spoon!')
      generator.next()
      

      Будет получено три следующих результата:

      Output

      {value: "Neo", done: false} {value: "There is no spoon!", done: true} {value: undefined, done: true}

      Метод return() заставил объект Generator завершить работу и проигнорировать все другие ключевые слова yield. Это особенно полезно в асинхронном программировании, когда необходимо, чтобы была возможность отмены для функции, например в случае прерывания веб-запроса, когда пользователь хочет выполнить другое действие, так как невозможно напрямую отменить Promise.

      Если тело функции генератора может перехватывать ошибки и работать с ними, можно использовать метод throw() для перебрасывания ошибки в генератор. Это действие запустит генератор, перебросит в него ошибку и прекратит работу генератора.

      Чтобы продемонстрировать это, мы поместим try...catch​​​ в тело функции генератора и зарегистрируем ошибку при ее наличии:

      // Define a generator function with a try...catch
      function* generatorFunction() {
        try {
          yield 'Neo'
          yield 'Morpheus'
        } catch (error) {
          console.log(error)
        }
      }
      
      // Invoke the generator and throw an error
      const generator = generatorFunction()
      

      Теперь мы запустим метод next()​​, за которым последует throw():

      generator.next()
      generator.throw(new Error('Agent Smith!'))
      

      Результат будет выглядеть следующим образом:

      Output

      {value: "Neo", done: false} Error: Agent Smith! {value: undefined, done: true}

      С помощью throw(), мы ввели ошибку в генератор, которая была перехвачена try...catch и зарегистрирована в консоли.

      Методы и состояния объекта генератора

      В следующей таблице представлен перечень методов, которые можно использовать на объектах Generator:

      Метод Описание
      next() Возвращает следующее значение генератора
      return() Возвращает значение генератора и прекращает работу генератора
      throw() Выдает ошибку и прекращает работу генератора

      В следующей таблице перечислены возможные состояния объекта Generator:

      Состояние Описание
      suspended Генератор остановил выполнение, но не прекратил работу
      closed Генератор прекратил выполнение из-за обнаружения ошибки, возвращения или итерации всех значений

      yield делегирование

      Помимо штатного оператора yield, генераторы могут также использовать выражение yield* для делегирования следующих значений другому генератору. Когда выражение yield* встречается в генераторе, оно входит в делегированный генератор и начинает итерацию по всем операторам yield до закрытия этого генератора. Это может быть использовано для разделения функций генератора для семантической организации кода, при этом итерация всех операторов yield будет происходить в правильном порядке.

      Для демонстрации мы можем создать две функции генератора, одна из которых будет yield* оператором для другой:

      // Generator function that will be delegated to
      function* delegate() {
        yield 3
        yield 4
      }
      
      // Outer generator function
      function* begin() {
        yield 1
        yield 2
        yield* delegate()
      }
      

      Далее, давайте проведем итерацию посредством функции begin():

      // Iterate through the outer generator
      const generator = begin()
      
      for (const value of generator) {
        console.log(value)
      }
      

      Это даст следующие значения в порядке их генерирования:

      Output

      1 2 3 4

      Внешний генератор выдал значения 1 и 2, затем делегировал другому генератору с yield*, который вернул 3 и 4.

      yield* также может делегировать любому итерируемому объекту, например Array или Map. Yield делегирование может быть полезным для организации кода, поскольку любая функция в рамках генератора, использующая yield, также должна быть генератором.

      Бесконечный поток данных

      Один из полезных аспектов генератора — способность работать с бесконечными потоками и коллекциями данных. Это можно увидеть на примере бесконечного цикла внутри функции генератора, который увеличивает число на 1.

      В следующем коде мы определяем функцию генератора и затем запускаем генератор:

      // Define a generator function that increments by one
      function* incrementer() {
        let i = 0
      
        while (true) {
          yield i++
        }
      }
      
      // Initiate the generator
      const counter = incrementer()
      

      Затем проводим итерацию значений с использованием next():

      // Iterate through the values
      counter.next()
      counter.next()
      counter.next()
      counter.next()
      

      Результат будет выглядеть следующим образом:

      Output

      {value: 0, done: false} {value: 1, done: false} {value: 2, done: false} {value: 3, done: false}

      Функция возвращает последовательные значения в бесконечном цикле, в то время как свойство done остается false, обеспечивая незавершенность.

      При использовании генераторов вам не нужно беспокоиться о создании бесконечного цикла, так как вы можете останавливать и возобновлять выполнение по своему усмотрению. Однако, вы все-таки должны быть осторожны с тем, как вы активируете генератор. Если вы используете оператор расширения или for...of для бесконечного потока данных, вы одновременно будете проводить итерацию бесконечного цикла, что приведет к отказу среды.

      Для более сложного примера бесконечного потока данных мы можем создать функцию генератора Fibonacci. Последовательность Фибоначчи, которая непрерывно складывает два предыдущих значения вместе, может быть записана с использованием бесконечного цикла в рамках генератора следующим образом:

      // Create a fibonacci generator function
      function* fibonacci() {
        let prev = 0
        let next = 1
      
        yield prev
        yield next
      
        // Add previous and next values and yield them forever
        while (true) {
          const newVal = next + prev
      
          yield newVal
      
          prev = next
          next = newVal
        }
      }
      

      Для тестирования мы можем создать цикл конечного числа и напечатать последовательность Фибоначчи в консоль.

      // Print the first 10 values of fibonacci
      const fib = fibonacci()
      
      for (let i = 0; i < 10; i++) {
        console.log(fib.next().value)
      }
      

      В результате вы получите следующий вывод:

      Output

      0 1 1 2 3 5 8 13 21 34

      Способность работать с бесконечными наборами данных — это одно из свойств, благодаря которым генераторы являются таким мощным инструментом. Эта способность может использоваться, например для установки бесконечной прокрутки на внешнем интерфейсе веб-приложений.

      Передача значений в генераторы

      В этой статье мы описывали использование генераторов в качестве итераторов и вырабатывали значения в каждой итерации. Помимо производства значений генераторы могут также потреблять значения от next(). В этом случае yield будет содержать значение.

      Важно отметить, что первый вызванный next() не будет передавать значение, а только запустит генератор. Для демонстрации этого мы можем записать значение yield и вызывать next() несколько раз с некоторыми значениями.

      function* generatorFunction() {
        console.log(yield)
        console.log(yield)
      
        return 'The end'
      }
      
      const generator = generatorFunction()
      
      generator.next()
      generator.next(100)
      generator.next(200)
      

      Результат будет выглядеть следующим образом:

      Output

      100 200 {value: "The end", done: true}

      Также возможно создать генератор с первоначальным значением. В следующем примере мы создадим цикл for и передадим каждое значение в метод next(), но также передадим аргумент в первоначальную функцию:

      function* generatorFunction(value) {
        while (true) {
          value = yield value * 10
        }
      }
      
      // Initiate a generator and seed it with an initial value
      const generator = generatorFunction(0)
      
      for (let i = 0; i < 5; i++) {
        console.log(generator.next(i).value)
      }
      

      Мы извлечем значение из next() и создадим новое значение в следующей итерации, которое является предыдущим значением,умноженным на десять. В результате вы получите следующий вывод:

      Output

      0 10 20 30 40

      Другой способ запуска генератора — завернуть генератор в функцию, которая всегда будет вызывать next() перед тем, как делать что-либо другое.

      async/await в генераторах

      Асинхронная функция — вид функции, имеющийся в ES6+ JavaScript, которая облегчает работу с асинхронными данными, делая их синхронными. Генераторы обладают более широким спектром возможностей, чем асинхронные функции, но способны воспроизводить аналогичное поведение. Реализация асинхронного программирования таким образом может повысить гибкость вашего кода.

      В этом разделе мы продемонстрируем пример воспроизведения async/await с генераторами.

      Давайте создадим асинхронную функцию, которая использует Fetch API для получения данных из JSONPlaceholder API (дает пример данных JSON для тестирования) и регистрирует ответ в консоли.

      Для начала определим асинхронную функцию под названием getUsers, которая получает данные из API и возвращает массив объектов, затем вызовем getUsers:

      const getUsers = async function() {
        const response = await fetch('https://jsonplaceholder.typicode.com/users')
        const json = await response.json()
      
        return json
      }
      
      // Call the getUsers function and log the response
      getUsers().then(response => console.log(response))
      

      Это даст данные JSON, аналогичные следующим:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      С помощью генераторов мы можем создать нечто почти идентичное, что не использует ключевые слова async/await. Вместо этого будет использоваться новая созданная нами функция и значения yield вместо промисов await.

      В следующем блоке кода мы определим функцию под названием getUsers, которая использует нашу новую функцию asyncAlt (будет описана позже) для имитации async/await.

      const getUsers = asyncAlt(function*() {
        const response = yield fetch('https://jsonplaceholder.typicode.com/users')
        const json = yield response.json()
      
        return json
      })
      
      // Invoking the function
      getUsers().then(response => console.log(response))
      

      Как мы видим, она выглядит почти идентично реализации async/await, за исключением того, что имеется функция генератора, которая передается в этих значениях функции yield.

      Теперь мы можем создать функцию asyncAlt, которая напоминает асинхронную функцию. asyncAlt​​​ имеет функцию генератора в качестве параметра и является нашей функцией, вырабатывающей промисы, которые получают возвраты. asyncAlt​​​ возвращает непосредственно функцию и решает каждый найденный промис до последнего:

      // Define a function named asyncAlt that takes a generator function as an argument
      function asyncAlt(generatorFunction) {
        // Return a function
        return function() {
          // Create and assign the generator object
          const generator = generatorFunction()
      
          // Define a function that accepts the next iteration of the generator
          function resolve(next) {
            // If the generator is closed and there are no more values to yield,
            // resolve the last value
            if (next.done) {
              return Promise.resolve(next.value)
            }
      
            // If there are still values to yield, they are promises and
            // must be resolved.
            return Promise.resolve(next.value).then(response => {
              return resolve(generator.next(response))
            })
          }
      
          // Begin resolving promises
          return resolve(generator.next())
        }
      }
      

      Это даст тот же результат, что и в версии async/await:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Обратите внимание, эта реализация предназначена для демонстрации того, как можно использовать генераторы вместо async/await, и не является готовой для эксплуатации конструкцией. В ней отсутствуют настройки обработки ошибок и нет возможности передавать параметры в выработанные значения. Хотя этот метод может сделать ваш код более гибким, async/await зачастую является более оптимальным вариантом, так как способен абстрагировать детали реализации и позволяет сконцентрироваться на написании продуктивного кода.

      Заключение

      Генераторы — это процессы, которые могут останавливать и возобновлять выполнение. Они являются мощной, универсальной, хотя и не слишком распространенной функцией JavaScript. В данном учебном пособии мы узнали о функциях и объектах генератора, методах, доступных для генераторов, операторах yield и yield*, а также генераторах, используемых с конечными и бесконечными массивами данных. Мы также изучили один способ реализации асинхронного кода без вложенных обратных вызовов или длинных цепочек промисов.

      Если вы хотите узнать больше о синтаксисе JavaScript, ознакомьтесь с учебными пособиями Понимание методов This, Bind, Call и Apply в JavaScript​​​ и Понимание объектов Map и Set в JavaScript.



      Source link