One place for hosting & domains

      Información

      Información sobre las bases de datos relacionales


      Introducción

      Los sistemas de administración de bases de datos (DBMS) son programas que permiten a los usuarios interactuar con una base de datos. Les permiten controlar el acceso a una base de datos, escribir datos, ejecutar consultas y realizar otras tareas relacionadas con la administración de bases de datos.

      Sin embargo, para realizar cualquiera de estas tareas, los DBMS deben tener algún tipo de modelo subyacente que defina la organización de los datos. El modelo relacional es un enfoque para la organización de datos que se ha utilizado ampliamente en programas de software de bases de datos desde su concepción a fines de la década de 1960; a tal grado que, a la fecha de redacción de este artículo, cuatro de los cinco DBMS más populares son relacionales.

      Este artículo conceptual describe la historia del modelo relacional, la manera en que se organizan los datos en las bases de datos relacionales y cómo se utilizan hoy en día.

      Historia del modelo relacional

      Las bases de datos son conjuntos de información, o datos, modelados de forma lógica. Cualquier recopilación de datos es una base de datos, independientemente de cómo o dónde se almacene. Incluso un gabinete de archivos con información sobre nómina es una base de datos, al igual que una pila de formularios de pacientes hospitalizados o la recopilación de información sobre clientes de una empresa repartida en varias ubicaciones. Antes de que almacenar y administrar datos con computadoras se convirtiera en una práctica común, las bases de datos físicas como estas eran lo único con lo que contaban las organizaciones gubernamentales y empresariales que necesitaban almacenar información.

      A mediados del siglo XX, los desarrollos en las ciencias de la computación dieron lugar a máquinas con mayor capacidad de procesamiento y almacenamiento, tanto local como externo. Estos avances hicieron que los especialistas en ciencias de la computación comenzaran a reconocer el potencial que tenían estas máquinas para almacenar y administrar cantidades de datos cada vez más grandes.

      Sin embargo, no había teorías sobre cómo las computadoras podían organizar datos de manera significativa y lógica. Una cosa es almacenar datos no ordenados en una máquina, pero es mucho más complicado diseñar sistemas que permitan agregar, recuperar, clasificar y administrar esos datos de forma sistemática y práctica. La necesidad de contar con un marco de trabajo lógico para almacenar y organizar datos dio lugar a varias propuestas sobre cómo utilizar las computadoras para administrar datos.

      Uno de los primeros modelos de bases de datos fue el modelo jerárquico, en el que los datos se organizan con una estructura de árbol similar a la de los sistemas de archivos modernos. El siguiente ejemplo muestra el aspecto que podría tener el diseño de una parte de una base de datos jerárquica utilizada para categorizar animales:

      Ejemplo de base de datos jerárquica: categorización de animales

      El modelo jerárquico se implementó ampliamente en los primeros sistemas de administración de bases de datos, pero resultaba poco flexible. En este modelo, a pesar de que los registros individuales pueden tener varios “elementos secundarios”, cada uno puede tener un solo “elemento primario” en la jerarquía. Es por eso que estas primeras bases de datos jerárquicas se limitaban a representar relaciones “uno a uno” y “uno a varios”. Esta carencia de relaciones “varios a varios” podía provocar problemas al trabajar con puntos de datos que se deseaba asociar a más de un elemento primario.

      A fines de los años 60, Edgar F. Codd, un especialista en ciencias de la computación que trabajaba en IBM, diseñó el modelo relacional de administración de bases de datos. El modelo relacional de Codd permitía que los registros individuales se relacionaran con más de una tabla y, de esta manera, posibilitaba las relaciones “varios a varios” entre los puntos de datos además de las relaciones “uno a varios”. Esto proporcionó más flexibilidad que los demás modelos existentes a la hora de diseñar estructuras de base de datos y permitió que los sistemas de gestión de bases de datos relacionales (RDBMS) pudieran satisfacer una gama mucho más amplia de necesidades empresariales.

      Codd propuso un lenguaje para la administración de datos relacionales, conocido como Alfa, que influyó en el desarrollo de los lenguajes de bases de datos posteriores. Dos colegas de Codd en IBM, Donald Chamberlin y Raymond Boyce, crearon un lenguaje similar inspirado en Alpha. Lo llamaron SEQUEL, abreviatura de Structured English Query Language (Lenguaje de consulta estructurado en inglés), pero debido a una marca comercial existente, lo abreviaron a SQL (conocido formalmente como* Lenguaje de consulta estructurado*).

      Debido a las limitaciones de hardware, las primeras bases de datos relacionales eran prohibitivamente lentas y el uso de la tecnología tardó un tiempo en generalizarse. Pero a mediados de los años ochenta, el modelo relacional de Codd se había implementado en varios productos comerciales de administración de bases de datos, tanto de IBM como de sus competidores. Estos proveedores también siguieron el liderazgo de IBM al desarrollar e implementar sus propios dialectos de SQL. Para 1987, tanto el Instituto Nacional Estadounidense de Estándares (American National Standards Institute) como la Organización Internacional de Normalización (International Organization for Standardization) habían ratificado y publicado normas para SQL, lo que consolidó su estado como el lenguaje aceptado para la administración de RDBMS.

      Gracias al uso extendido del modelo relacional en varias industrias, se lo comenzó a reconocer como el modelo estándar para la administración de datos. Incluso con el surgimiento de varias bases de datos NoSQL en los últimos años, las bases de datos relacionales siguen siendo las herramientas predominantes para almacenar y organizar datos.

      Cómo organizan los datos las bases de datos relacionales

      Ahora que tiene una idea general de la historia del modelo relacional, vamos a analizar en detalle cómo organiza los datos.

      Los elementos más fundamentales del modelo relacional son las relaciones, que los usuarios y los RDBMS modernos reconocen como tablas. Una relación es un conjunto de tuplas, o filas de una tabla, en el que cada una de ellas comparte un conjunto de atributos o columnas:

      Diagrama de ejemplo de la asociación entre las relaciones, las tuplas y los atributos

      Una columna es la estructura de organización más pequeña de una base de datos relacional y representa las distintas facetas que definen los registros en la tabla. De ahí proviene su nombre más formal: atributos. Se puede pensar que cada tupla es como una instancia única de cualquier tipo de personas, objetos, eventos o asociaciones que contenga la tabla.  Estas instancias pueden ser desde empleados de una empresa y ventas de un negocio en línea hasta resultados de pruebas de laboratorio. Por ejemplo, en una tabla que contiene registros de los maestros de una escuela, las tuplas pueden tener atributos como name, subjects, start_date, etc.

      Al crear una columna, especificamos un* tipo de datos *que determina el tipo de entradas que se permiten en esa columna. Los sistemas de administración de bases de datos relacionales (RDBMS) suelen implementar sus propios tipos de datos únicos, que pueden no ser directamente intercambiables con tipos de datos similares de otros sistemas. Algunos tipos de datos frecuentes son fechas, cadenas, enteros y booleanos.

      En el modelo relacional, cada tabla contiene, por lo menos, una columna que puede utilizarse para identificar de forma única cada fila, lo que se denomina clave primaria. Esto es importante, dado que significa que los usuarios no necesitan saber dónde se almacenan físicamente los datos en una máquina; en su lugar, sus DBMS pueden realizar un seguimiento de cada registro y devolverlo según corresponda. A su vez, significa que los registros no tienen un orden lógico definido y que los usuarios tienen la capacidad de devolver sus datos en cualquier orden y a través de los filtros que deseen.

      Si tiene dos tablas que desea asociar, una manera de hacerlo es con una clave externa. Una clave externa es, básicamente, una copia de la clave primaria de una tabla (la tabla “primaria”) insertada en una columna de otra tabla (la “secundaria”). El siguiente ejemplo destaca la relación entre dos tablas: una se utiliza para registrar información sobre los empleados de una empresa y la otra, para realizar un seguimiento de las ventas de la empresa. En este ejemplo, la clave primaria de la tabla EMPLOYEES se utiliza como clave externa de la tabla SALES:

      Diagrama de ejemplo de cómo la clave primaria de la tabla EMPLOYEE actúa como la clave externa de la tabla SALES

      Si intenta agregar un registro a la tabla secundaria y el valor ingresado en la columna de la clave externa no existe en la clave primaria de la tabla primaria, la instrucción de inserción no será válida. Esto ayuda a mantener la integridad del nivel de las relaciones, dado que las filas de ambas tablas siempre se relacionarán correctamente.

      Los elementos estructurales del modelo relacional ayudan a mantener los datos almacenados de forma organizada, pero el almacenamiento de datos solo es útil si se pueden recuperar. Para obtener información de un RDBMS, puede emitir una consulta o una solicitud estructurada de un conjunto de información. Como se mencionó anteriormente, la mayoría de las bases de datos relacionales utilizan SQL para administrar y consultar datos. SQL permite filtrar y manipular los resultados de las consultas con una variedad de cláusulas, predicados y expresiones, lo que, a su vez, permite controlar correctamente qué datos se mostrarán en el conjunto de resultados.

      Ventajas y limitaciones de las bases de datos relacionales

      Teniendo en cuenta la estructura organizativa subyacente de las bases de datos relacionales, consideremos algunas de sus ventajas y desventajas.

      Hoy en día, tanto SQL como las bases de datos que lo implementan se desvían del modelo relacional de Codd de diversas maneras. Por ejemplo, el modelo de Codd determina que cada fila de una tabla debe ser única, mientras que, por cuestiones de practicidad, la mayoría de las bases de datos relacionales modernas permiten la duplicación de filas. Algunas personas no consideran que las bases de datos SQL sean verdaderas bases de datos relacionales a menos que cumplan con las especificaciones de Codd del modelo relacional. Sin embargo, en términos prácticos, es probable que cualquier DBMS que utilice SQL y, por lo menos, se adhiera en cierta medida al modelo relacional se denomine “sistema de administración de bases de datos relacionales”.

      A pesar de que las bases de datos relacionales ganaron popularidad rápidamente, algunas de las deficiencias del modelo relacional comenzaron a hacerse evidentes a medida que los datos se volvieron más valiosos y las empresas comenzaron a almacenar más de ellos. Entre otras cosas, puede ser difícil escalar una base de datos relacional de forma horizontal.  Los términos escalado horizontal o escala horizontal se refieren a la práctica de agregar más máquinas a una pila existente para extender la carga y permitir un mayor tráfico y un procesamiento más rápido. Se suele contrastar con el escalado vertical, que implica la actualización del hardware de un servidor existente, generalmente, añadiendo más RAM o CPU.

      El motivo por el cual es difícil escalar una base de datos relacional de forma horizontal está relacionado con el hecho de que el modelo relacional se diseñó para garantizar coherencia, lo que significa que los clientes que realizan consultas en la misma base de datos siempre obtendrán los mismos datos. Al querer escalar una base de datos relacional de forma horizontal en varias máquinas, resulta difícil asegurar la coherencia, dado que los clientes pueden escribir datos en un nodo pero no en otros.  Es probable que haya un retraso entre la escritura inicial y el momento en que se actualicen los demás nodos para reflejar los cambios, lo que daría lugar a inconsistencias entre ellos.

      Otra limitación que presentan los RDBMS es que el modelo relacional se diseñó para administrar datos estructurados o alineados con un tipo de datos predeterminado o, por lo menos, organizado de una manera predeterminada para que se puedan ordenar o buscar de forma más sencilla. Sin embargo, con la difusión de los equipos informáticos personales y el auge de Internet a principios de la década de 1990, los datos no estructurados, como los mensajes de correo electrónico, las fotos, los videos, etc., se volvieron más comunes.

      Nada de esto significa que las bases de datos relacionales no sean útiles. Al contrario, después de más de 40 años, el modelo relacional sigue siendo el marco dominante para la administración de datos. Su prevalencia y longevidad indican que las bases de datos relacionales son una tecnología madura, lo que constituye una de sus principales ventajas. Hay muchas aplicaciones diseñadas para trabajar con el modelo relacional, así como muchos administradores de bases de datos profesionales expertos en bases de datos relacionales. También hay una amplia variedad de recursos disponibles en formato impreso y digital para quienes desean comenzar a utilizar bases de datos relacionales.

      Otra ventaja de las bases de datos relacionales es que casi todos los RDBMS admiten transacciones. Las transacciones constan de una o más instrucciones SQL individuales que se ejecutan en secuencia como una unidad de trabajo única. Las transacciones tienen un enfoque de todo o nada, lo que significa que todas las instrucciones SQL de una transacción deben ser válidas; de lo contrario, falla en su totalidad. Esto es muy útil para garantizar la integridad de los datos al realizar cambios en varias filas o tablas.

      Por último, las bases de datos relacionales son sumamente flexibles. Se han utilizado para crear una amplia variedad de aplicaciones distintas y siguen funcionando de forma eficiente incluso con grandes cantidades de datos. SQL también es sumamente potente y permite agregar y cambiar datos sobre la marcha, así como alterar la estructura de los esquemas y las tablas de las bases de datos sin afectar a los datos existentes.

      Conclusión

      Gracias a su flexibilidad y diseño para la integridad de los datos, más de cincuenta años después de su concepción inicial, las bases de datos relacionales siguen siendo la principal forma de administrar y almacenar datos.  A pesar del surgimiento de diversas bases de datos NoSQL en los últimos años, la comprensión del modelo relacional y la manera de trabajar con RDBMS es fundamental para cualquier persona que desee crear aplicaciones que aprovechen el poder de los datos.

      Para obtener más información sobre RDBMS populares de código abierto, le recomendamos consultar nuestra comparación de diversas bases de datos relacionales SQL de código abierto. Si desea obtener más información sobre las bases de datos en general, le recomendamos consultar nuestra biblioteca completa de contenidos relacionados con bases de datos.



      Source link

      Información sobre los parámetros predeterminados en JavaScript


      El autor seleccionó el COVID-19 Relief Fund para que reciba una donación como parte del programa Write for DOnations.

      Introducción

      En ECMAScript 2015, se introdujeron parámetros de función predeterminados en el lenguaje JavaScript. Permiten a los desarrolladores inicializar una función con valores predeterminados si no se proporcionan argumentos para invocarla. El hecho de inicializar parámetros de función de esta manera hace que sus funciones sean más fáciles de leer y menos propensas a errores, y proporciona su comportamiento predeterminado. Lo ayudará a evitar los errores que se originan al pasar argumentos undefined y de desestructurar objetos que no existen.

      A lo largo de este artículo, revisará la diferencia entre parámetros y argumentos, aprenderá a usar parámetros predeterminados en funciones, conocerá maneras alternativas de admitir parámetros predeterminados y aprenderá los tipos de valores y expresiones que pueden usarse como parámetros predeterminados. También, verá ejemplos que demuestran cómo funcionan los parámetros predeterminados en JavaScript.

      Argumentos y parámetros

      Antes de explicar los parámetros de función predeterminados, es importante comprender los valores que pueden adoptar estos parámetros por defecto. Por eso, primero revisaremos la diferencia entre los argumentos y los parámetros de una función. Si desea obtener más información sobre esta distinción, lea el artículo anterior de nuestra serie de JavaScript Cómo definir funciones en JavaScript.

      En el siguiente bloque de código, creará una función que devuelve el cubo de un número dado que se define como x:

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

      La variable x de este ejemplo es un parámetro, una variable con nombre que se pasa a una función. Los parámetros siempre deben encontrarse en una variable y nunca deben tener un valor directo.

      Observé el siguiente bloque de código, que invoca la función cube que acaba de crear:

      // Invoke cube function
      cube(10)
      

      Esto generará el siguiente resultado:

      Output

      1000

      En este caso, 10 es un argumento, es decir, un valor que se pasa a una función cuando se la invoca. El valor también se suele encontrar dentro de una variable, como se puede observar en el siguiente ejemplo:

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

      El resultado es el mismo:

      Output

      1000

      Si una función que espera un argumento no lo recibe, usará implícitamente el valor undefined:

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

      El resultado es el siguiente:

      Output

      NaN

      En este caso, cube() intenta calcular el valor de undefined * undefined * undefined, lo que da como resultado NaN o “not a number”. Para obtener más información al respecto, consulte la sección de números en Información sobre los tipos de datos en JavaScript.

      A veces, este comportamiento automático de las funciones puede ser problemático. En algunos casos, puede ser conveniente que el parámetro tenga un valor, incluso si no se pasó ningún argumento a la función. Esos son los casos en los que la función parámetros predeterminados resulta útil, un tema que se tratará en la siguiente sección.

      Sintaxis de los parámetros predeterminados

      Con la incorporación de parámetros predeterminados de ES2015, ahora, puede asignar un valor predeterminado a cualquier parámetro, que la función usará en vez de undefined cuando se la invoque sin argumento. En esta sección, primero, se indicará el procedimiento manual y, luego, se brindará orientación para definir los parámetros predeterminados.

      Sin parámetros predeterminados, debería buscar específicamente los valores undefined para establecer valores predeterminados, tal como se indica en este ejemplo:

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

      Aquí, se usa una instrucción condicional para corroborar si el valor se proporcionó automáticamente como undefined y, luego, se establece el valor de x en 5. Se obtendrá el siguiente resultado:

      Output

      125

      Por el contrario, si se usan parámetros predeterminados, se logra el mismo objetivo con mucho menos código. Puede establecer un valor predeterminado para el parámetro en cube al asignarle el operador de asignación de equivalencia (=), resaltado en el siguiente ejemplo:

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

      Ahora, cuando la función cube se invoque sin argumento, asignará 5 a x y mostará el cálculo en vez de NaN:

      // Invoke cube function without an argument
      cube()
      

      Output

      125

      Seguirá funcionando según lo previsto cuando se pase un argumento e ignorará el valor predeterminado:

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

      Output

      8

      Sin embargo, una salvedad a tener en cuenta es que el valor del parámetro predeterminado también anulará el valor undefined explícito que se pase como argumento a una función, como se demuestra a continuación:

      // Invoke cube function with undefined
      cube(undefined)
      

      Se obtendrá un cálculo con x igual a 5:

      Output

      125

      En este caso, se calcularon los valores de los parámetros predeterminados y el valor undefined explícito no los anuló.

      Ahora que tiene una idea de la sintaxis básica de los parámetros predeterminados, en la sección siguiente, se indicará cómo funcionan con distintos tipos de datos.

      Tipos de datos de los parámetros predeterminados

      Se puede usar cualquier valor primitivo u objeto como valor de un parámetro predeterminado. En esta sección, verá que esta flexibilidad aumenta las formas en que se pueden utilizar los parámetros predeterminados.

      Primero, establezca los parámetros usando un número, una cadena, un booleano, un objeto, una matriz y un valor nulo como predeterminado. En este ejemplo, se usa la sintaxis arrow function:

      // 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)
      

      Al invocar estas funciones sin parámetros, todas usarán los valores predeterminados:

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

      Output

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

      Tenga en cuenta que los objetos que se creen con un parámetro predeterminado se crearán cada vez que se invoque la función. Un uso común de los parámetros predeterminados es aprovechar este comportamiento para obtener valores de los objetos. Si intenta acceder a un valor o desestructurarlo desde un objeto que no existe, obtendrá un error. Sin embargo, si el parámetro predeterminado es un objeto vacío, simplemente, se darán valores undefined en vez de devolver un error:

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

      Esto evitará el error que genera la desestructuración de objetos que no existen.

      Ahora que aprendió cómo funcionan los parámetros predeterminados con distintos tipos de datos, en la siguiente sección, se explicará el funcionamiento conjunto de varios parámetros predeterminados.

      Uso de varios parámetros predeterminados

      Puede usar tantos parámetros predeterminados como desee en una función. En esta sección, aprenderá a hacerlo y a usarlos para manipular el DOM con un ejemplo práctico.

      Primero, declare una función sum() con varios parámetros predeterminados:

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

      Esto dará como resultado el siguiente cálculo predeterminado:

      Output

      3

      Además, el valor que se use en un parámetro se puede utilizar en cualquier parámetro predeterminado subsiguiente, de izquierda a derecha. Por ejemplo, la función createUser crea un objeto de usuario userObj como tercer parámetro y lo único que hace la función en sí misma es mostrar userObj con los primeros dos parámetros:

      // 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')
      

      Si invoca la función user aquí, obtendrá el siguiente resultado:

      Output

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

      Se recomienda dejar todos los parámetros predeterminados al final de la lista de parámetros para poder omitir valores opcionales con facilidad. Si, primero, usa un parámetro predeterminado, deberá pasar explícitamente undefined para usar el valor por defecto.

      El ejemplo a continuación muestra el parámetro predeterminado al comienzo de la lista:

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

      Al invocar esta función, debería invocar defaultFirst() con dos argumentos:

      defaultFirst(undefined, 2)
      

      El resultado sería el siguiente:

      Output

      3

      En este ejemplo se muestra el parámetro predeterminado al final de la lista:

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

      El resultado sería el mismo:

      Output

      3

      Las dos funciones tienen el mismo resultado, pero la que tiene el valor predeterminado al final de la lista permite invocar la función de forma mucho más ordenada.

      En este ejemplo práctico, la función creará un elemento DOM y agregará una etiqueta de texto y clases, si existen.

      // 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
      }
      

      Puede invocar la función con algunas clases en una matriz:

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

      Al invocar greeting, se obtendrá el siguiente valor:

      Output

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

      Sin embargo, si no incluye la matriz classNames en la llamada de función, de todos modos, funcionará.

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

      Ahora, greeting2 tiene el siguiente valor:

      Output

      <p>Hello!</p>

      En este ejemplo, forEach() se puede usar en una matriz vacía sin ningún problema. Si la matriz vacía no se hubiera establecido en el parámetro predeterminado, obtendría el siguiente error:

      Output

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

      Ahora que aprendió cómo pueden interactuar varios parámetros predeterminados, puede avanzar a la siguiente sección para ver cómo funcionan las llamadas de función como parámetros predeterminados.

      Llamadas de función como parámetros predeterminados

      Además de los tipos primitivos y los objetos, el resultado de una llamada de función se puede usar como parámetro predeterminado.

      En el siguiente bloque de código, creará una función que mostrará un número al azar y, luego, usará el resultado como valor del parámetro predeterminado en una función cube:

      // 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
      }
      

      Al invocar la función cube sin un parámetro, es posible que se obtengan resultados diferentes cada vez que se invoque.

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

      El resultado de estas llamadas de función variará:

      Output

      512 64

      Incluso puede usar métodos incorporados, como los que del objeto Math, y utilizar el valor devuelto en una llamada de función como parámetro en otra función.

      En el siguiente ejemplo, se asigna un número al azar a x, que se usa como parámetro en la función cube que creó. Luego, el parámetro y calculará la raíz cúbica del número y corroborará si x e y son equivalentes:

      // 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()
      

      Esto dará el siguiente resultado:

      Output

      true

      Como se puede observar en el siguiente ejemplo, un parámetro predeterminado puede ser, incluso, una definición de función, que define un parámetro como función inner y devuelve la llamada de función de 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

      La función inner se creará de cero cada vez que se invoque la función outer.

      Conclusión

      En este artículo, conoció los parámetros de función predeterminados y aprendió a usarlos. Ahora, puede usar parámetros predeterminados como ayuda para mantener sus funciones ordenadas y fáciles de leer. También sabe asignar matrices y objetos vacíos a parámetros de antemano para reducir la complejidad y la cantidad de líneas de código en tareas como la recuperación de valores de un objeto o la repetición de una matriz.

      Si desea obtener más información sobre JavaScript, consulte la página de inicio de nuestra serie Cómo producir código en JavaScript o busque nuestra serie Cómo producir código en Node.js para hallar artículos sobre el desarrollo de backend.



      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