Introducción a ECMAScript 2017 (ES8)

Tabla de contenidos


Prólogo
Descripción general de ES7
1. Entradas de objeto
2. Valores del objeto
3. String.prototype.padEnd
4. String.prototype.padStart
5. Object.getOwnPropertyDescriptor
6. Comas finales
7. SharedArrayBuffer
8. Atomics
9. Funciones asíncronas

Prólogo


Hola, en el pasado ya consideraba las innovaciones en ES6 y ahora es el momento de desarmar ES8 ya que trajo muchas cosas nuevas. No consideré ES7 (2016) por separado, ya que esta versión trajo solo 2 innovaciones. Este es Array.prototype.includes () y el operador de exponenciación. Pero aún así, antes de comenzar ES8, veamos las innovaciones de ES7.

Descripción general de ES7


El método incluye () determina si la matriz contiene un elemento específico, devolviendo verdadero o falso dependiendo de esto.

Array.prototype.includes(searchElement[, fromIndex = 0]) : Boolean 

searchElement: el elemento a buscar.

fromIndex: la posición en la matriz desde la que comenzar a buscar el elemento searchElement. Para valores negativos, la búsqueda se realiza comenzando con el índice array.length + fromIndex ascendente. El valor predeterminado es 0.

Ejemplos

 [1, 2, 3].includes(2); // true [1, 2, 3].includes(4); // false [1, 2, 3].includes(3, 3); // false [1, 2, 3].includes(3, -1); // true [1, 2, NaN].includes(NaN); // true 

Incluye () se puede aplicar a otros tipos de objetos (por ejemplo, objetos en forma de matriz). Ejemplo: usar el método incluye () en un objeto de argumentos.

 (function() { console.log([].includes.call(arguments, 'a')); // true console.log([].includes.call(arguments, 'd')); // false })('a','b','c'); 

El operador de exponenciación (**) devuelve una potencia con base ay un exponente natural b. Elevar a al poder de b.

 a ** b 

Ejemplos

 2 ** 3 // 8 3 ** 2 // 9 3 ** 2.5 // 15.588457268119896 10 ** -1 // 0.1 NaN ** 2 // NaN 2 ** 3 ** 2 // 512 2 ** (3 ** 2) // 512 (2 ** 3) ** 2 // 64 -(2 ** 2) // -4 (-2) ** 2 // 4 

1. Entradas de objeto


Object.entries () devuelve una matriz cuyos elementos son matrices correspondientes a la propiedad enumerada del par [clave, valor] que se encuentra directamente en el objeto. El orden de las propiedades es el mismo que cuando recorre las propiedades de un objeto manualmente.

 Object.entries(obj) : Array 

obj: un objeto cuyas propiedades enumeradas se devolverán como una matriz [clave, valor].

Object.entries () devuelve las propiedades en el mismo orden que en el bucle for ... in (la diferencia es que for-in también enumera las propiedades de la cadena del prototipo). El orden de los elementos en la matriz que Object.entries () devuelve es independiente de cómo se declara el objeto. Si se necesita un orden específico, la matriz debe ordenarse antes de llamar al método.

Ejemplos

 var obj = { foo: "bar", baz: 42 }; console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ] //    var obj = { 0: 'a', 1: 'b', 2: 'c' }; console.log(Object.entries(obj)); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ] //    c random   var an_obj = { 100: 'a', 2: 'b', 7: 'c' }; console.log(Object.entries(an_obj)); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ] // getFoo  ,    var my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; } } }); my_obj.foo = "bar"; console.log(Object.entries(my_obj)); // [ ['foo', 'bar'] ] // non-object     object console.log(Object.entries("foo")); // [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ] let obj = { one: 1, two: 2 }; for (let [k,v] of Object.entries(obj)) console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`) // "one": 1 // "two": 2 

Convertir objeto a mapa

El nuevo constructor Map () acepta la repetición de valores. Con Object.entries, puede convertir fácilmente Object a Map. Esto es más conciso que usar una matriz de matrices de 2 elementos, pero las claves solo pueden ser cadenas.

 var obj = { foo: "bar", baz: 42 }; var map = new Map(Object.entries(obj)); console.log(map); // Map {"foo" => "bar", "baz" => 42} 

¿Por qué el valor de retorno de Object.entries () es una matriz y no un iterador?
El caso de uso correspondiente en este caso es Object.keys (), y no, por ejemplo, Map.prototype.entries ().

¿Por qué Object.entries () devuelve solo propiedades nativas enumeradas con claves de cadena?

Nuevamente, esto se hace para que coincida con Object.keys (). Este método también ignora las propiedades cuyas claves son caracteres. Al final, puede haber un método Reflect.ownEntries () que devuelve todas sus propias propiedades.

Ver object.entries en la especificación oficial, así como en MDN Web Docs .

2. Valores del objeto


Object.values ​​() devuelve una matriz cuyos elementos son los valores de las propiedades enumeradas que se encuentran en el objeto. El orden es el mismo que si recorre el objeto manualmente.

 Object.values(obj) : Array 

obj: un objeto cuyos valores de las propiedades enumeradas se devolverán.

El método Object.values ​​() devuelve una matriz de valores de las propiedades enumeradas del objeto en el mismo orden que el bucle for ... in. La diferencia entre un bucle y un método es que el bucle enumera propiedades desde y desde la cadena de prototipos.

Ejemplos

 var obj = { foo: "bar", baz: 42 }; console.log(Object.values(obj)); // ['bar', 42] //    var obj = { 0: 'a', 1: 'b', 2: 'c' }; console.log(Object.values(obj)); // ['a', 'b', 'c'] 

La diferencia entre Object.entries y Object.values ​​() es que el primero devuelve una matriz de matrices que contienen el nombre y el valor de la propiedad, mientras que la segunda solo devuelve una matriz con el valor de las propiedades.

Ejemplo de diferencia entre Object.values ​​() y Object.entries ()

 const object = { a: 'somestring', b: 42, c: false }; console.log(Object.values(object)); // ["somestring", 42, false] console.log(Object.entries(object)); // [ ["a", "somestring"], ["b", 42], ["c", false] ] 

Consulte Object.values ​​() en la especificación oficial, así como en MDN Web Docs .

3. String.prototype.padEnd


El método padEnd () completa la línea actual con la cadena dada (eventualmente repitiéndose) para que la cadena resultante alcance la longitud especificada. La adición se aplica al final (derecha) de la línea actual.

 String.prototype.padEnd(maxLength [ , fillString ]) : String 

maxLength: la longitud de la fila resultante después de que se haya rellenado la fila actual. Si este parámetro es menor que la longitud de la línea actual, la línea actual se devolverá tal cual.
fillString: una cadena para complementar la línea actual. Si esta línea es demasiado larga, se truncará y se aplicará la izquierda. "" (0x0020 SPACE) es el valor predeterminado para este parámetro.

Ejemplos

 'abc'.padEnd(10); // "abc " 'abc'.padEnd(10, "foo"); // "abcfoofoof" 'abc'.padEnd(6,"123456"); // "abc123" 

Los casos de uso para llenar cadenas incluyen:

  • Agregar un contador o identificador a un nombre de archivo o URL: 'archivo 001.txt'
  • Alineación de salida de la consola: "Prueba 001: ✓"
  • Imprima números hexadecimales o binarios con un número fijo de dígitos: '0x00FF'

Consulte String.prototype.padEnd en la especificación oficial, así como en MDN Web Docs .

4. String.prototype.padStart


El método padStart () llena la línea actual con otra línea (varias veces, si es necesario) para que la línea resultante alcance la longitud especificada. El llenado se lleva a cabo al comienzo (izquierda) de la línea actual.

 String.prototype.padStart(maxLength [, fillString]) : String 

maxLength: la longitud de la línea de resumen después de completar la línea actual. Si el valor es menor que la longitud de la línea actual, la línea actual se devolverá sin cambios.

fillString: una cadena para llenar la línea actual. Si esta cadena es demasiado larga para la longitud dada, se truncará. El valor predeterminado es "" (0x0020 SPACE).

Ejemplos

 'abc'.padStart(10); // " abc" 'abc'.padStart(10, "foo"); // "foofoofabc" 'abc'.padStart(6,"123465"); // "123abc" 'abc'.padStart(8, "0"); // "00000abc" 'abc'.padStart(1); // "abc" 

¿Por qué los métodos de relleno no se llaman padLeft y padRight?

Para los idiomas bidireccionales o de derecha a izquierda, los términos "izquierda" y "derecha" no funcionan. Por lo tanto, la denominación de padStart y padEnd sigue los nombres existentes que comienzan con beginWith y endsWith.

Consulte String.prototype.padStart en la especificación oficial, así como en MDN Web Docs .

5. Object.getOwnPropertyDescriptor


El método Object.getOwnPropertyDescriptor () devuelve un descriptor de propiedad para su propia propiedad (es decir, una ubicada directamente en el objeto y no recibida a través de la cadena del prototipo) del objeto pasado. Si la propiedad no existe, devuelve indefinido.

 Object.getOwnPropertyDescriptor(obj, prop) : Object 

obj: el objeto en el que se busca la propiedad.

prop: el nombre de la propiedad cuya descripción se devolverá.

Este método le permite ver la descripción exacta de la propiedad. Una propiedad en JavaScript consta de un nombre de cadena y un descriptor de propiedad.

Un descriptor de propiedad es un registro con algunos de los siguientes atributos:

  • valor: el valor asociado con la propiedad (solo en el descriptor de datos).
  • escribible: verdadero si el valor asociado con la propiedad se puede cambiar; de lo contrario, falso (solo en el descriptor de datos).
  • get: una función que devuelve el valor de la propiedad, o no está definida si no existe dicha función (solo en el descriptor de acceso).
  • set: una función que cambia el valor de una propiedad, o no está definida si no existe tal función (solo en el descriptor de acceso).
  • configurable: verdadero si el tipo de identificador de esta propiedad se puede cambiar y si la propiedad se puede eliminar del objeto que la contiene; de ​​lo contrario, es falso.
  • enumerable: verdadero si esta propiedad está disponible al enumerar las propiedades del objeto que lo contiene; de ​​lo contrario, es falso.

Ejemplos

 obj = { get foo() { return 10; } }; console.log(Object.getOwnPropertyDescriptor(obj, 'foo')); // {set: undefined, enumerable: true, configurable: true, get: ƒ} obj2 = { bar: 42 }; console.log(Object.getOwnPropertyDescriptor(obj2, 'bar')); // {value: 42, writable: true, enumerable: true, configurable: true} 

Casos de uso para Object.getOwnPropertyDescriptor ()


Primer caso de uso: copiar propiedades a un objeto
Comenzando con ES6, JavaScript ya tiene un método de herramienta para copiar propiedades: Object.assign (). Sin embargo, este método utiliza operaciones simples de obtención y configuración para copiar una propiedad cuya clave es la clave:

 const value = source[key]; // get target[key] = value; // set 
Esto significa que no copia correctamente las propiedades con atributos distintos a los especificados por defecto (métodos para obtener, configurar, escribir, etc.). El siguiente ejemplo ilustra esta limitación. La fuente del objeto tiene un instalador cuya clave es foo:
 const source = { set foo(value) { console.log(value); } }; console.log(Object.getOwnPropertyDescriptor(source, 'foo')); // { get: undefined, set: [Function: foo], enumerable: true, configurable: true } 

El uso de Object.assign () para copiar la propiedad foo en el objeto de destino falla:

 const target1 = {}; Object.assign(target1, source); console.log(Object.getOwnPropertyDescriptor(target1, 'foo')); // { value: undefined, writable: true, enumerable: true, configurable: true } 
Afortunadamente, el uso de Object.getOwnPropertyDescriptors () junto con Object.defineProperties () funciona:

 const target2 = {}; Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source)); console.log(Object.getOwnPropertyDescriptor(target2, 'foo')); // { get: undefined, set: [Function: foo], enumerable: true, configurable: true } 

Segundo caso de uso: clonación de objetos
La clonación superficial es similar a las propiedades de copia, por lo que Object.getOwnPropertyDescriptors () también es una buena opción aquí.

Esta vez usamos Object.create (), que tiene dos parámetros:
El primer parámetro especifica el prototipo del objeto devuelto.

Un segundo parámetro opcional es una colección de descriptores de propiedades, similares a los devueltos por Object.getOwnPropertyDescriptors ().

 const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); 

Tercer caso de uso: literales de objetos multiplataforma con prototipos arbitrarios.

La mejor manera sintáctica de usar un objeto literal para crear un objeto con un prototipo arbitrario es usar la propiedad especial __proto__:

 const obj = { __proto__: prot, foo: 123, }; 

Por desgracia, esta característica está garantizada para estar presente solo en los navegadores. La solución general es Object.create () y asignación:

 const obj = Object.create(prot); obj.foo = 123; 

Pero también puede usar Object.getOwnPropertyDescriptors ():

 const obj = Object.create( prot, Object.getOwnPropertyDescriptors({ foo: 123, }) ); 

Otra alternativa es Object.assign ():

 const obj = Object.assign( Object.create(prot), { foo: 123, } ); 

Dificultad: copiar métodos usando super.

El método que super utiliza está estrechamente vinculado a su objeto de inicio (el objeto en el que se almacena). Actualmente no hay forma de copiar o mover dicho método a otro objeto.

Consulte Object.getOwnPropertyDescriptor en la especificación oficial, así como en MDN Web Docs .

6. Comas finales


Comas colgantes (comas finales): pueden ser útiles al agregar nuevos elementos, parámetros o propiedades al código JavaScript. Si desea agregar una nueva propiedad, simplemente agregue una nueva línea sin cambiar la anterior, si la coma colgante ya está en ella. Esto hace que las diferencias en el control de versiones sean más limpias y los cambios de código pueden ser menos problemáticos.

Comas colgantes en literales


Matrices

JavaScript ignora las comas colgantes en las matrices:

 var arr = [ 0, 1, 2, ]; console.log(arr); // [0, 1, 2] console.log(arr.length); // 3 var arr2 = [0, 1, 2,,,]; console.log(arr2.length); // 5 arr2.forEach((e) => console.log(e)); // 0 1 2 console.log(arr.map((e) => e)); // 0 1 2 

Si se usa más de un punto colgante, se crearán agujeros. Una matriz con "agujeros" se llama dispersa (una matriz densa no tiene "agujeros"). Al iterar una matriz utilizando, por ejemplo, Array.prototype.forEach () o Array.prototype.map (), se omitirán los agujeros.

Los objetos

 var object = { foo: "bar", baz: "qwerty", age: 42, }; console.log(object); // {foo: "bar", baz: "qwerty", age: 42} 

Comas colgantes en funciones


Parámetro Definición

Las siguientes definiciones de parámetros de función son válidas y equivalentes entre sí. Las comas colgantes no afectan la propiedad de longitud de una función o sus argumentos objeto.

 function f(p) {} function f(p,) {} (p) => {}; (p,) => {}; 

Definición del método

La coma colgante también funciona con métodos de definición para clases u objetos.

 class C { one(a,) {}, two(a, b,) {}, } var obj = { one(a,) {}, two(a, b,) {}, }; 

Llamada a la función

Las siguientes llamadas a funciones son válidas y equivalentes entre sí.

 f(p); f(p,); Math.max(10, 20); Math.max(10, 20,); 

Comas colgantes inválidas

Definir parámetros de función o llamar a una función que contenga solo una coma generará un SyntaxError. Además, cuando se usan los parámetros restantes, no se permiten comas colgantes.

 function f(,) {} // SyntaxError: missing formal parameter (,) => {}; // SyntaxError: expected expression, got ',' f(,) // SyntaxError: expected expression, got ',' function f(...p,) {} // SyntaxError: parameter after rest parameter (...p,) => {} // SyntaxError: expected closing parenthesis, got ',' 

Comas colgantes en la desestructuración


Las comas colgantes también se pueden usar a la izquierda cuando se usa una asignación destructiva.

 //      [a, b,] = [1, 2]; //      var o = { p: 42, q: true, }; var {p, q,} = o; 

Una vez más, utilizando los parámetros restantes, se lanzará un SyntaxError.

 var [a, ...b,] = [1, 2, 3]; // Uncaught SyntaxError: Rest element must be last element 

JSON Dangling Comas


Las comas colgantes en un objeto solo están permitidas en ECMAScript 5. Dado que JSON se basa en la sintaxis de JavaScript anterior a ES5, las comas colgantes no están permitidas en JSON.

Ambas líneas arrojan un SyntaxError

 JSON.parse('[1, 2, 3, 4, ]'); JSON.parse('{"foo" : 1, }'); // Uncaught SyntaxError: Unexpected token ] in JSON // Uncaught SyntaxError: Unexpected token } in JSON 

¿Por qué son útiles las comas colgantes?


Hay dos beneficios.

En primer lugar, reorganizar los elementos es más fácil porque no es necesario agregar o quitar comas si el último elemento cambia su posición.

En segundo lugar, ayuda a los sistemas de control de versiones a realizar un seguimiento de lo que realmente ha cambiado. Por ejemplo, de:

 [ 'Foo' ] : [ 'Foo', '' ] 

hace que tanto la línea con 'foo' como la línea con 'barra' se marquen como modificadas, aunque el único cambio real es agregar la última línea.

Ver Comillas finales en MDN Web Docs .

7. SharedArrayBuffer


El objeto SharedArrayBuffer se usa para crear un búfer dividido de longitud fija para almacenar datos binarios primitivos, similar al objeto ArrayBuffer, pero en contraste, las instancias SharedArrayBuffer se pueden usar para crear una vista en la memoria compartida. SharedArrayBuffer no se puede desconectar.
 new SharedArrayBuffer(length) : Object 
longitud: el tamaño, en bytes, para crear la matriz de búfer.

return - Un nuevo objeto SharedArrayBuffer de la longitud especificada. Su contenido después de la inicialización es 0.

PostMessage y la clonación estructurada se utilizan para dividir la memoria utilizando un objeto SharedArrayBuffer entre un agente en el clúster y otro (el agente puede ser el programa principal de la página web o uno de los trabajadores web).

El algoritmo de clonación estructurada acepta SharedArrayBuffers y TypedArrays asignados a SharedArrayBuffers. En ambos casos, el objeto SharedArrayBuffer se pasa al receptor, lo que crea un nuevo objeto privado SharedArrayBuffer dentro del agente receptor (igual que para ArrayBuffer). Sin embargo, el bloque de datos compartidos al que hacen referencia ambos objetos SharedArrayBuffer es el mismo bloque de datos, y los efectos de terceros en el bloque en uno de los agentes eventualmente serán visibles en el otro agente.

 var sab = new SharedArrayBuffer(1024); worker.postMessage(sab); 

La memoria compartida se puede crear y cambiar simultáneamente en los trabajadores o en el hilo principal. Dependiendo del sistema (CPU, SO, navegador), puede llevar tiempo hasta que los cambios se propaguen a todos los contextos. Para la sincronización, se necesitan operaciones atómicas.

Shared Array Buffers es un bloque de construcción primitivo para abstracciones de paralelismo de nivel superior. Le permiten compartir los bytes de un objeto SharedArrayBuffer entre varios trabajadores y el subproceso principal (el búfer se comparte para acceder a los bytes, envolverlo en una matriz con tipo). Este tipo de intercambio tiene dos ventajas:
Puede intercambiar datos entre trabajadores más rápido.

La coordinación entre los trabajadores se vuelve más fácil y rápida (en comparación con postMessage ()).

La implementación del trabajador es la siguiente.

 // worker.js self.addEventListener ('message', function (event) { const {sharedBuffer} = event.data; const sharedArray = new Int32Array (sharedBuffer); // ··· }); 

Primero, extraemos el búfer de la matriz compartida que nos enviaron y luego lo envolvemos en una matriz tipada para que podamos usarlo localmente.

Propiedades y métodos de SharedArrayBuffer.

SharedArrayBuffer.length: la longitud del constructor SharedArrayBuffer cuyo valor es 1.
SharedArrayBuffer.prototype: permite propiedades adicionales para todos los objetos SharedArrayBuffer.

Instancias SharedArrayBuffer
Las propiedades

SharedArrayBuffer.prototype.constructor: define una función que crea un prototipo de un objeto. El valor inicial es el constructor estándar SharedArrayBuffer incorporado.

SharedArrayBuffer.prototype.byteLength (solo lectura): el tamaño de la matriz en bytes. Esto se establece cuando se crea la matriz y no se puede cambiar.

Métodos

SharedArrayBuffer.prototype.slice () - Devuelve un nuevo SharedArrayBuffer cuyo contenido es una copia de los bytes de este SharedArrayBuffer desde el principio, incluido hasta el final, de la exclusiva. Si el principio o el final es negativo, esto se refiere al índice desde el final de la matriz, no desde el principio. Este método tiene el mismo algoritmo que Array.prototype.slice ().

 //  SharedArrayBuffer     const buffer = new SharedArrayBuffer(16); const int32View = new Int32Array(buffer); //  view // produces Int32Array [0, 0, 0, 0] int32View[1] = 42; const sliced = new Int32Array(buffer.slice(4,12)); console.log(sliced); // Int32Array [42, 0] 

 sab.slice([begin, end]) : Object 

begin: el índice cero en el que comienza la extracción. Puede usar un índice negativo que indique el desplazamiento desde el final de la secuencia. slice (-2) extrae los dos últimos elementos en una secuencia. Si el comienzo no está definido, el segmento comienza en el índice 0.
Fin: el índice de base cero en el que se debe completar la extracción.

Por ejemplo, el segmento (1,4) recupera el segundo elemento a través del cuarto elemento (elementos con los índices 1, 2 y 3). Puede usar un índice negativo que indique el desplazamiento desde el final de la secuencia. slice (2, -1) recupera el tercer elemento a través del penúltimo elemento de la secuencia. Si se omite el final, corte las recuperaciones hasta el final de la secuencia (sab.byteLength).

Ejemplos

 var sab = new SharedArrayBuffer(1024); sab.slice(); // SharedArrayBuffer { byteLength: 1024 } sab.slice(2); // SharedArrayBuffer { byteLength: 1022 } sab.slice(-2); // SharedArrayBuffer { byteLength: 2 } sab.slice(0, 1); // SharedArrayBuffer { byteLength: 1 } 

Consulte SharedArrayBuffer en la especificación oficial, así como en los documentos web de MDN .

8. Atomics


El objeto Atomics proporciona operaciones atómicas como métodos estáticos. Usado con un objeto SharedArrayBuffer.

Las operaciones atómicas se instalan en el módulo Atomics. A diferencia de otros objetos globales, Atomics no es un constructor. No se puede usar con el nuevo operador o para llamar a un objeto Atomics como una función. Todas las propiedades y métodos de Atomics son estáticos (como un objeto matemático, por ejemplo).

Cuando se comparte la memoria, varios hilos pueden leer y escribir los mismos datos en la memoria. Las operaciones atómicas garantizan que los valores esperados se escribirán y leerán, y las operaciones se completarán antes de que la próxima operación comience su trabajo, y no se interrumpirán.

Las propiedades


Atomics [Symbol.toStringTag]: el valor de esta propiedad es Atomics.

Métodos


Operaciones atómicas

  • Atomics.add (): agrega el valor presentado al valor actual en la posición especificada en la matriz. Devuelve el valor anterior en esta posición.
  • Atomics.and (): calcula AND bit a bit en la posición de matriz especificada. Devuelve el valor anterior en esta posición.
  • Atomics.compareExchange (): guarda el valor presentado en la posición especificada de la matriz, si es equivalente al valor presentado. Devuelve el valor anterior.
  • Atomics.exchange() — . .
  • Atomics.load() — .
  • Atomics.or() — OR . .
  • Atomics.store() — . .
  • Atomics.sub() — . .
  • Atomics.xor() — XOR . .

El método estático Atomics.add () agrega el valor al actual en la posición especificada en la matriz y devuelve el valor anterior en esta posición. Esta operación atómica asegura que no se produzca otra escritura hasta que el valor modificado se vuelva a escribir.

 Atomics.add(typedArray, index, value) : mixed 

  • typedArray: una matriz dividida de enteros. Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array o Uint32Array.
  • index: la posición en typedArray para agregar valor.
  • valor: el número a agregar.
  • return: el valor anterior en la posición especificada (typedArray [index]).

  • Lanza un TypeError si el tipo typedArray no es uno de los tipos enteros válidos.
  • Lanza un TypeError si el tipo typedArray no es de tipo genérico.
  • Lanza RangeError si el índice está fuera de typedArray.

Ejemplos

 var sab = new SharedArrayBuffer(1024); var ta = new Uint8Array(sab); Atomics.add(ta, 0, 12); //  0,   Atomics.load(ta, 0); // 12 

Atomics.add () en la especificación , en MDN Web Docs .

Espera y notifica


Los métodos wait () y wake () se modelan sobre la base de futexes ("mutex de espacio de usuario rápido") Linux y proporcionan formas de esperar un momento cuando un cierto estado no se vuelve verdadero, y generalmente se usa como construcciones de bloqueo.

Atomics.wait ()
Comprueba si el valor que aún está representado está contenido en la posición especificada de la matriz y está inactivo o en espera. Devuelve ok, no igual o expirado. Si la espera no está permitida en el agente que realiza la llamada, arrojará un error de excepción (la mayoría de los navegadores no permiten la espera () en la secuencia principal del navegador).

  • Atomics.wait() — , -. «ok», «not-equal» «timed-out». , ( wait() ).
  • Atomics.wake() — , . , .
  • Atomics.isLockFree(size) — , . true, ( ). .

Problemas de optimización


La optimización hace que el código sea impredecible entre los trabajadores. En subprocesos individuales, los compiladores pueden realizar optimizaciones que rompen el código multiproceso.

Tomemos, por ejemplo, el siguiente código:

 while (sharedArray [0] === 123); 

En un solo subproceso, el valor de sharedArray [0] nunca cambia durante la ejecución del bucle (si sharedArray es una matriz o una matriz con tipo que no se ha corregido de ninguna manera). Por lo tanto, el código se puede optimizar de la siguiente manera:

 const tmp = sharedArray [0]; while (tmp === 123); 

Sin embargo, en modo multiproceso, esta optimización no nos permite usar esta plantilla para esperar los cambios realizados en otro hilo.

Otro ejemplo es el siguiente código:

 // main.js sharedArray [1] = 11; sharedArray [2] = 22; 

En un hilo, puede reorganizar estas operaciones de escritura porque no se lee nada entre ellas. Varios subprocesos experimentan problemas cuando espera que las grabaciones se realicen en un orden específico:

 // worker.js while (sharedArray [2]! == 22); console.log (sharedArray [1]); // 0  11 

Estos tipos de optimización hacen que sea casi imposible sincronizar las acciones de varios trabajadores que trabajan en el mismo búfer con una matriz común.

Resolviendo problemas de optimización


Usando la variable global Atomics, cuyos métodos tienen tres usos principales.

Primer caso de uso: sincronización.

Los métodos atómicos se pueden usar para sincronizar con otros trabajadores. Por ejemplo, las siguientes dos operaciones le permiten leer y escribir datos y los compiladores nunca las reordenan:

 Atomics.load (TypedArray <T>, index) : T Atomics.store (TypedArray <T>, index, value: T) : T 

La idea es utilizar operaciones ordinarias para leer y escribir la mayoría de los datos, mientras que las operaciones de Atomics (carga, almacenamiento y otros) aseguran que la lectura y la escritura sean seguras. A menudo utilizará sus propios mecanismos de sincronización, como bloqueos, que se basan en Atomics.

Este es un ejemplo muy simple que siempre funciona gracias a Atomics (omití la configuración sharedArray):

 // main.js console.log ('notified...'); Atomics.store (sharedArray, 0, 123); // worker.js while (Atomics.load (sharedArray, 0)! == 123); console.log ('notified'); 

Segundo caso de uso: esperando notificación .

Usar un ciclo while para esperar una notificación no es muy eficiente, por lo que Atomics tiene operaciones que ayudan: Atomics.wait (Int32Array, index, value, timeout) y Atomics.wake (Int32Array, index, count).

Tercer caso de uso: operaciones atómicas
Algunas operaciones atómicas realizan operaciones aritméticas y no pueden interrumpirse al mismo tiempo, lo que ayuda con la sincronización. Por ejemplo:

 Atomics.add (TypedArray <T>, index, value) : T 

En términos generales, esta operación realiza: índice + = valor;

Problema con valores rotos.

Otro efecto problemático con la memoria compartida son los valores rotos (basura): al leer, puede ver un valor intermedio, ni el valor anterior al nuevo valor se escribió en la memoria, ni el nuevo valor.

La sección de Lecturas sin rasgaduras de la especificación establece que no hay vacíos si y solo si:

  • Tanto la lectura como la escritura se realizan a través de matrices escritas (no DataViews).
  • Ambas matrices escritas están alineadas con sus memorias intermedias de matrices compartidas: sharedArray.byteOffset% sharedArray.BYTES_PER_ELEMENT === 0
  • Ambas matrices escritas tienen el mismo número de bytes por elemento.

En otras palabras, los valores desgarrados son un problema cuando se accede al mismo búfer de una matriz compartida a través de:

  • Uno o más DateViews;
  • Hay una o más matrices tipadas no alineadas;
  • Arreglos mecanografiados con diferentes tamaños de elementos;

Para evitar una brecha en los valores en estos casos, use Atomics o sync.

Buffers de matriz compartidos en usos


Buffers de matriz compartidos y semántica de JavaScript para ejecutar una función pendiente. JavaScript tiene la llamada semántica de ejecución "antes de la finalización": cada función puede esperar que no sea interrumpida por otro hilo hasta que se complete. Las funciones se convierten en transacciones y pueden ejecutar algoritmos completos, mientras que nadie ve los datos con los que trabajan en un estado intermedio.

Los Buffers de matriz compartida interrumpen el ciclo hasta su finalización (RTC): los datos en los que la función está trabajando pueden ser cambiados por otro hilo durante la ejecución de la función. Sin embargo, el código controla por completo si se produce esta violación de RTC: si no utiliza Buffers de matriz compartida, es seguro.

Esto es más o menos similar a cómo las funciones asincrónicas violan RTC. Allí habilita la operación de bloqueo con la palabra clave wait.

Los Buffers de matriz compartida permiten que emscripten compile pthreads en asm.js. Citando la página de documentación de emscripten:

[Es] [Los Buffers de matriz compartidos permiten] Las aplicaciones de Emscripten comparten el montón de memoria principal entre los trabajadores web. Esto, junto con primitivas para atómicos de bajo nivel y compatibilidad con futex, permite que Emscripten implemente la compatibilidad con la API Pthreads (hilos POSIX).

[Ru] [Los Buffers de matriz compartidos permiten] Las aplicaciones Emscripten comparten un montón de memoria principal entre los trabajadores web. Junto con las primitivas atómicas de bajo nivel y el soporte futex, Emscripten permite el soporte para la API Pthreads (hilos POSIX).

Es decir, puede compilar código C y C ++ multiproceso en asm.js.

Hay una discusión en curso sobre la mejor manera de usar el subprocesamiento múltiple en WebAssembly. Dado que los trabajadores web son relativamente pesados, es posible que WebAssembly introduzca hilos livianos. También puede ver que los temas están en camino hacia el futuro de WebAssembly.

Intercambiar datos que no sean enteros


Por el momento, solo se pueden usar matrices de enteros (de hasta 32 bits de longitud). Esto significa que la única forma de compartir otros tipos de datos es codificarlos como enteros. Las herramientas que pueden ayudar incluyen:

  • TextEncoder y TextDecoder: el primero convierte cadenas a instancias Uint8Array, el segundo hace lo contrario.
  • stringview.js: , . .
  • FlatJS: JavaScript (, ) (ArrayBuffer SharedArrayBuffer). JavaScript + FlatJS JavaScript. JavaScript (TypeScript . .) .
  • TurboScript: JavaScript- . asm.js WebAssembly.

Al final, es probable que aparezcan mecanismos adicionales de alto nivel para el intercambio de datos. Y los experimentos continuarán descubriendo cómo deberían ser estos mecanismos.

¿Cuánto más rápido funciona el código que usa búferes de matriz compartida?

Lars T. Hansen escribió dos implementaciones del algoritmo Mandelbrot (como se describe en su artículo " A Taste of JavaScript's New Parallel Primitives ", una versión secuencial y una versión paralela que utiliza varios trabajadores web. Hasta 4 trabajadores web y, por lo tanto, núcleos de procesador, aceleración aumenta casi linealmente, de 6.9 cuadros por segundo (1 trabajador web) a 25.4 cuadros por segundo (4 trabajadores web). Más trabajadores web traen mejoras de productividad adicionales, pero más modestas.

Hansen señala que las aceleraciones son impresionantes, pero el trabajo paralelo se debe a un código más complejo.

Información adicional sobre Buffers de matriz compartida y tecnologías de soporte:


Otras tecnologías de concurrencia de JavaScript:


Consulte Objeto Atomics en la especificación oficial , así como en los documentos web de MDN .

9. Funciones asíncronas


Crear una función Async usando el constructor AsyncFunction


El constructor AsyncFunction crea un nuevo objeto de función asíncrona. En JavaScript, cualquier función asincrónica es en realidad un objeto AsyncFunction.

Tenga en cuenta que AsyncFunction no es un objeto global. Se puede obtener ejecutando el siguiente código.

 Object.getPrototypeOf(async function(){}).constructor 

Sintaxis

 new AsyncFunction([arg1[, arg2[, ...argN]],] functionBody) 

arg1, arg2, ... argN: nombres utilizados por la función como nombres de argumentos formales. Cada nombre debe ser una cadena que coincida con un identificador de JavaScript válido o una lista de tales cadenas separadas por comas; por ejemplo, "x", "theValue" o "a, b".

functionBody: una cadena que contiene la definición de una función en el código fuente de JavaScript.

Los objetos de función asíncrona creados con el constructor AsyncFunction se analizarán en el momento en que se cree la función. Esto es menos eficiente que declarar una función asincrónica usando la expresión de función asíncrona y llamarla dentro de su código, ya que dichas funciones se analizan con el resto del código.

Todos los argumentos pasados ​​a la función se tratan como los nombres de los identificadores de parámetros en la función creada en el orden en que se pasan.

Llamar al constructor AsyncFunction como una función (sin usar el nuevo operador) tiene el mismo efecto que llamarlo como un constructor.

Las funciones asíncronas creadas con el constructor AsyncFunction no provocan un cortocircuito en los contextos que las crean; Siempre se crean en el ámbito global. Cuando comiencen, solo podrán acceder a sus variables locales y variables globales, pero no tendrán acceso a los ámbitos en los que se llamó al constructor AsyncFunction. Esto es diferente de usar eval con el código para la función asíncrona.

Ejemplo de creación de una función asíncrona utilizando el constructor AsyncFunction

 function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor var a = new AsyncFunction('a', 'b', 'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);'); a(10, 20).then(v => { console.log(v); //  30  4  }); 

Declaración de función asíncrona


La declaración de función asincrónica define una función asincrónica que devuelve un objeto AsyncFunction. También puede definir funciones asíncronas utilizando la expresión de función asíncrona.

Sintaxis

 async function name([param[, param[, ... param]]]) { // body } 

nombre: el nombre de la función.
param: el nombre del argumento que se pasará a la función.
declaraciones - Una expresión que contiene el cuerpo de la función.

Después de la llamada, la función asíncrona devuelve Promise. Cuando se ha recibido el resultado, Promise se completa y devuelve el valor recibido. Cuando la función asíncrona arroja una excepción, Promise fallará con un valor de lanzamiento.

La función asíncrona puede contener una expresión de espera que detiene la ejecución de la función asíncrona y espera una respuesta de la Promesa aprobada, luego reanuda la función asíncrona y devuelve el valor recibido.

La palabra clave await es válida solo en funciones asincrónicas. En otro contexto, obtendrá un error SyntaxError.

El propósito de las funciones asíncrono / espera es simplificar el uso de promesas sincrónicamente y jugar alguna acción en el grupo Promesas. Al igual que las promesas son como devoluciones de llamada estructuradas, async / waitit es como una combinación de generadores y promesas.

Ejemplo

 function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } async function add1(x) { const a = await resolveAfter2Seconds(20); const b = await resolveAfter2Seconds(30); return x + a + b; } add1(10).then(v => { console.log(v); //  60  4  }); async function add2(x) { const a = resolveAfter2Seconds(20); const b = resolveAfter2Seconds(30); return x + await a + await b; } add2(10).then(v => { console.log(v); //  60  2  }); 

¿Para qué sirve la asincronía?


Puede escribir su programa JS en un archivo .js, pero su código probablemente se dividirá en varias partes. Y solo una de las partes se ejecutará ahora, y el resto se ejecutará más adelante. La función es la técnica más utilizada para dividir un programa en partes.

El principal problema de la mayoría de los desarrolladores que ven a JS por primera vez es la falta de comprensión de lo que no sucederá inmediatamente después de ahora. En otras palabras, las tareas que no se pueden completar ahora, por definición, finalizarán de forma asincrónica. Y no tendremos el comportamiento de bloqueo del programa que asumimos. (You-Dont-Know-JS / async & performance, Jake Archibald).

 // ajax(..)  Ajax-,   var data = ajax( "http://some.url.1" ); console.log( data );// !  `data`     Ajax- 

¿Cuál es el error aquí? console.log () ejecutado antes de recibir los datos de la solicitud.

La decisión obvia de "esperar" de ahora en adelante es usar devoluciones de llamada:

 ajax( "http://some.url.1", function myCallbackFunction(data){ console.log( data ); // ,   ! } ); 

Considere varios métodos para resolver la ejecución de código síncrono prematuramente.

Tenemos 3 funciones getUser, getPosts, getComments.

 const { getUser, getPosts, getComments } = require('./db'); getUser(1, (error, user) => { if(error) return console.error(error); getPosts(user.id, (error, posts) => { if(error) return console.error(error); getComments(posts[0].id, (error, comment) => { if(error) return console.error(error); console.log(comments); }); }); }); 

En este ejemplo, es difícil no notar la pirámide, que aumenta con la incorporación de nuevas funciones. Este estilo de codificación comúnmente se llama Callback Hell . Este es un cierto patrón que le proporciona control sobre las solicitudes competidoras (asíncronas), lo que garantiza la secuencia de su ejecución.

Parte de la solución al problema de las funciones de anidamiento es usar Promise (que discutí en mi último artículo , que lo elimina y hace que el código sea más limpio. También proporcionan una forma más conveniente de manejar errores. Pero a muchos no les gustó esta sintaxis.

 getUser(1) .then(user => getPosts(user,id)) .then(posts => getComments(posts[0].id)) .then(comments => console.log(comments)) .catch(error => console.error(error)); 

Los generadores se convirtieron en una alternativa a Promise (que también examiné en un artículo anterior . El generador en sí no es adecuado para escribir código asincrónico, pero si los usa junto con Promise, obtenemos algo único: código asincrónico que parece sincrónico. Al mismo tiempo, los generadores proporcionan el conocido mecanismo de manejo de errores que usa la construcción try ... catch. Solo los generadores tienen una gran desventaja: para usarlos con Promise necesitará una función separada que controlará el proceso del generador R. Puede escribir esta función usted mismo o usar una biblioteca de terceros, por ejemplo, co . En este ejemplo, escribí mi implementación de dicha función.

 co(function* () { try { let user = yield getUser(1); let posts = yield getPosts(user.id); let comments = yield getComments(posts[0].id); console.log(comments); } catch (error) { console.log(error); } }); function co(generator) { const iterator = generator(); return new Promise((resolve, reject) => { function run(prev) { const { value, done } = iterator.next(prev); if (done) resolve(value); else if (value instanceof Promise) value.then(run, reject); else run(value); } run(); }); } 

Cada uno de los métodos para trabajar con código asincrónico tiene sus ventajas y desventajas.
Funciones de devolución de llamada (funciones de devolución de llamada): fácil de usar, pero con un aumento en las funciones anidadas, la legibilidad comienza a sufrir.

Promesas (promesas): elegantes y cómodas, pero difíciles de entender para los principiantes.

Generadores (Generadores): le permiten escribir código asíncrono sincrónicamente, pero requieren una función separada, y el mecanismo de operación de los generadores es muy complicado.

Las funciones asincrónicas se crearon sobre la base de Promesas y Generadores, para que el trabajo con código asincrónico sea simple y comprensible.

Para comprender qué son las funciones asincrónicas, considere el siguiente ejemplo:

 function getUser(id) { return { id: 1 }; } let user = getUser(1); console.log(user); // { id: 1 } 

Ahora, si hace que la función sea asíncrona (agregando la palabra clave asíncrona), la función devolverá una Promesa que contiene un objeto con la propiedad id.

 async function getUser(id) { return { id: 1 }; } let user = getUser(1); console.log(user); // Promise { {id: 1} } 

Por lo tanto, podemos decir que cualquier función asincrónica devuelve Promis (o más bien envuelve en Promis el valor que debería devolver). Si el valor devuelto a la función asincrónica ya es una promesa, entonces no se volverá a cambiar.

Para obtener el valor de la promesa, podemos usar el método then ().

 async function getUser(id) { return { id: 1 }; } getUser(1) .then(user => console.log(user)); // { id: 1 } 

O podemos usar la palabra clave wait, que se discutirá más adelante.

Volvamos a nuestro primer ejemplo (solo que esta vez usaremos la función real para enviar una solicitud HTTP.

 fetch(`https://jsonplaceholder.typicode.com/users/1`) .then(data => data.json()) .then(data => console.log(data)); 

Así es como se ve el código asincrónico usando Promise.
Pero podemos escribir código asíncrono como síncrono si usamos funciones asíncronas.

 async function sendRequest() { let response= await fetch(`https://jsonplaceholder.typicode.com/users/1`); return response.json(); } async function main() { var a = await sendRequest(); console.log(a); } main(); 

Lo único que no me gusta es que el operador asíncrono solo se puede usar en funciones asincrónicas. De lo contrario, no necesitaría usar la función main (). Por supuesto, también puede usar el método then (), pero el código ya no se verá asincrónico.

 async function sendRequest() { let response= await fetch(`https://jsonplaceholder.typicode.com/users/1`); return response.json(); } sendRequest() .then((data) => console.log(data)); 

La conclusión es que no utilizamos funciones de devolución de llamada para obtener datos de fetch (). En su lugar, usamos la palabra clave await, que, por así decirlo, le dice al tiempo de ejecución: espere a que la función fetch () se ejecute y escriba el resultado en la variable de respuesta. Y usando la función de devolución de llamada, decimos: espere a que se ejecute la función fetch () y llame a la función de devolución de llamada para procesar los datos.

Aquí está la diferencia obvia entre usar Promise y la función asíncrona

 //  Promise function sendRequest() { return fetch(`https://jsonplaceholder.typicode.com/users/1`) .then(data => data.json()); } //  async function async function sendRequest() { let response = await fetch(`https://jsonplaceholder.typicode.com/users/1`); return response.json(); } 

El operador de espera solo se puede usar en el cuerpo de funciones asincrónicas, y su acción se puede usar en cualquier función que devuelva una promesa.

Para manejar excepciones en funciones asincrónicas, es costumbre usar la construcción try ... catch.

 async function sendRequest() { let response = await fetch(`https://jsonplaceholder.typicode.com/users/1`); try { throw new Error("Unexpected error"); return response.json(); } catch(error) { console.log(error); // Error: Unexpected error at sendRequest } } 

Y finalmente ...

  //    async; await; async; await; async; await; async; await; In the System(); The function.sleep()s tonight~ 

Consulte las definiciones de funciones asíncronas en la especificación oficial , así como en los documentos web de MDN .

Source: https://habr.com/ru/post/475074/


All Articles