Introduction à ECMAScript 2017 (ES8)

Table des matières


Préface
Présentation d'ES7
1. Entrées d'objet
2. Object.values
3. String.prototype.padEnd
4. String.prototype.padStart
5. Object.getOwnPropertyDescriptor
6. Virgules de fin
7. SharedArrayBuffer
8. Atomics
9. Fonctions asynchrones

Préface


Bonjour, dans le passé, j'avais déjà envisagé des innovations dans ES6 et maintenant il est temps de démonter ES8 car il a apporté beaucoup de nouvelles choses. Je n'ai pas considéré ES7 (2016) séparément, car cette version n'a apporté que 2 innovations. Il s'agit de Array.prototype.includes () et de l'opérateur d'exponentiation. Mais avant de commencer ES8, regardons les innovations d'ES7.

Présentation d'ES7


La méthode includes () détermine si le tableau contient un élément spécifique, renvoyant vrai ou faux selon cela.

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

searchElement - L'élément à rechercher.

fromIndex - La position dans le tableau à partir de laquelle commencer la recherche de l'élément searchElement. Pour les valeurs négatives, la recherche est effectuée en commençant par l'index array.length + fromIndex croissant. La valeur par défaut est 0.

Des exemples

 [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 

includes () peut être appliqué à d'autres types d'objets (par exemple, des objets de type tableau). Exemple: utilisation de la méthode includes () sur un objet arguments.

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

L'opérateur d'exponentiation (**) renvoie une puissance de base a et d'exposant naturel b. Élever a au pouvoir de b.

 a ** b 

Des exemples

 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. Entrées d'objet


Object.entries () renvoie un tableau dont les éléments sont des tableaux correspondant à la propriété énumérée de la paire [clé, valeur] trouvée directement dans l'objet. L'ordre des propriétés est le même que lorsque vous parcourez manuellement les propriétés d'un objet.

 Object.entries(obj) : Array 

obj - Un objet dont les propriétés énumérées seront renvoyées sous forme de tableau [clé, valeur].

Object.entries () renvoie les propriétés dans le même ordre que dans la boucle for ... in (la différence est que for-in répertorie également les propriétés de la chaîne de prototypes). L'ordre des éléments du tableau renvoyé par Object.entries () est indépendant de la façon dont l'objet est déclaré. Si un ordre spécifique est nécessaire, le tableau doit être trié avant l'appel de la méthode.

Des exemples

 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 un objet en carte

Le nouveau constructeur Map () accepte la répétition des valeurs. Avec Object.entries, vous pouvez facilement convertir un objet en carte. C'est plus concis que d'utiliser un tableau de tableaux à 2 éléments, mais les clés ne peuvent être que des chaînes.

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

Pourquoi la valeur de retour de Object.entries () est-elle un tableau et non un itérateur?
Le cas d'utilisation correspondant dans ce cas est Object.keys (), et non, par exemple, Map.prototype.entries ().

Pourquoi Object.entries () retourne uniquement les propriétés natives énumérées avec des clés de chaîne?

Encore une fois, cela est fait pour faire correspondre Object.keys (). Cette méthode ignore également les propriétés dont les clés sont des caractères. En fin de compte, il peut y avoir une méthode Reflect.ownEntries () qui renvoie toutes ses propres propriétés.

Voir object.entries dans la spécification officielle, ainsi que dans les documents Web MDN .

2. Object.values


Object.values ​​() renvoie un tableau dont les éléments sont les valeurs des propriétés énumérées trouvées dans l'objet. L'ordre est le même que si vous parcourez l'objet manuellement.

 Object.values(obj) : Array 

obj - Un objet dont les valeurs des propriétés énumérées seront renvoyées.

La méthode Object.values ​​() renvoie un tableau de valeurs des propriétés énumérées de l'objet dans le même ordre que la boucle for ... in. La différence entre une boucle et une méthode est que la boucle répertorie les propriétés de et de la chaîne prototype.

Des exemples

 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 différence entre Object.entries et Object.values ​​() est que le premier retourne un tableau de tableaux contenant le nom et la valeur de la propriété, tandis que le second retourne uniquement un tableau avec la valeur des propriétés.

Exemple de différence entre Object.values ​​() et 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] ] 

Voir Object.values ​​() dans la spécification officielle, ainsi que dans MDN Web Docs .

3. String.prototype.padEnd


La méthode padEnd () complète la ligne actuelle avec la chaîne donnée (éventuellement répétée) afin que la chaîne résultante atteigne la longueur spécifiée. L'addition est appliquée à la fin (à droite) de la ligne actuelle.

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

maxLength - La longueur de la ligne résultante après le remplissage de la ligne actuelle. Si ce paramètre est inférieur à la longueur de la ligne actuelle, la ligne actuelle sera renvoyée telle quelle.
fillString - Une chaîne pour compléter la ligne actuelle. Si cette ligne est trop longue, elle sera tronquée et sa partie la plus à gauche sera appliquée. "" (0x0020 ESPACE) est la valeur par défaut de ce paramètre.

Des exemples

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

Les cas d'utilisation pour le remplissage de chaînes incluent:

  • Ajout d'un compteur ou d'un identifiant à un nom de fichier ou à une URL: «fichier 001.txt»
  • Alignement de la sortie de la console: «Test 001: ✓»
  • Imprimer des nombres hexadécimaux ou binaires avec un nombre fixe de chiffres: '0x00FF'

Voir String.prototype.padEnd dans la spécification officielle, ainsi que dans MDN Web Docs .

4. String.prototype.padStart


La méthode padStart () remplit la ligne actuelle avec une autre ligne (plusieurs fois, si nécessaire) afin que la ligne résultante atteigne la longueur spécifiée. Le remplissage s'effectue au début (à gauche) de la ligne courante.

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

maxLength - La longueur de la ligne récapitulative après la fin de la ligne actuelle. Si la valeur est inférieure à la longueur de la ligne actuelle, la ligne actuelle sera retournée inchangée.

fillString - Une chaîne pour remplir la ligne actuelle. Si cette chaîne est trop longue pour la longueur donnée, elle sera tronquée. La valeur par défaut est "" (0x0020 ESPACE).

Des exemples

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

Pourquoi les méthodes de remplissage ne sont-elles pas appelées padLeft et padRight?

Pour les langues bidirectionnelles ou de droite à gauche, les termes «gauche» et «droite» ne fonctionnent pas. Par conséquent, la dénomination de padStart et padEnd suit les noms existants commençant par beginWith et endsWith.

Voir String.prototype.padStart dans la spécification officielle, ainsi que dans MDN Web Docs .

5. Object.getOwnPropertyDescriptor


La méthode Object.getOwnPropertyDescriptor () renvoie un descripteur de propriété pour sa propre propriété (c'est-à-dire située directement dans l'objet et non reçue via la chaîne de prototype) de l'objet transmis. Si la propriété n'existe pas, retourne undefined.

 Object.getOwnPropertyDescriptor(obj, prop) : Object 

obj - L'objet dans lequel la propriété est recherchée.

prop - Le nom de la propriété dont la description sera retournée.

Cette méthode vous permet de visualiser la description exacte de la propriété. Une propriété en JavaScript se compose d'un nom de chaîne et d'un descripteur de propriété.

Un descripteur de propriété est un enregistrement avec certains des attributs suivants:

  • value - La valeur associée à la propriété (uniquement dans le descripteur de données).
  • accessible en écriture - true si la valeur associée à la propriété peut être modifiée, sinon false (uniquement dans le descripteur de données).
  • get - Une fonction qui renvoie la valeur de la propriété, ou indéfinie s'il n'y a pas une telle fonction (uniquement dans le descripteur d'accès).
  • set - Une fonction qui change la valeur d'une propriété, ou indéfinie s'il n'y a pas une telle fonction (uniquement dans le descripteur d'accès).
  • configurable - true si le type de handle de cette propriété peut être modifié et si la propriété peut être supprimée de l'objet qui la contient, sinon false.
  • enumerable - true si cette propriété est disponible lors de l'énumération des propriétés de l'objet qui la contient, sinon false.

Des exemples

 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} 

Cas d'utilisation de Object.getOwnPropertyDescriptor ()


Premier cas d'utilisation: copier les propriétés dans un objet
Depuis ES6, JavaScript dispose déjà d'une méthode d'outil pour copier les propriétés: Object.assign (). Cependant, cette méthode utilise de simples opérations get et set pour copier une propriété dont la clé est la clé:

 const value = source[key]; // get target[key] = value; // set 
Cela signifie qu'il ne copie pas correctement les propriétés avec des attributs autres que ceux spécifiés par défaut (méthodes d'obtention, de définition, d'écriture, etc.). L'exemple suivant illustre cette limitation. La source de l'objet a un programme d'installation dont la clé est foo:
 const source = { set foo(value) { console.log(value); } }; console.log(Object.getOwnPropertyDescriptor(source, 'foo')); // { get: undefined, set: [Function: foo], enumerable: true, configurable: true } 

Échec de l'utilisation d'Object.assign () pour copier la propriété foo dans l'objet cible:

 const target1 = {}; Object.assign(target1, source); console.log(Object.getOwnPropertyDescriptor(target1, 'foo')); // { value: undefined, writable: true, enumerable: true, configurable: true } 
Heureusement, l'utilisation d'Object.getOwnPropertyDescriptors () avec Object.defineProperties () fonctionne:

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

Deuxième cas d'utilisation: clonage d'objets
Le clonage superficiel est similaire à la copie de propriétés, donc Object.getOwnPropertyDescriptors () est également un bon choix ici.

Cette fois, nous utilisons Object.create (), qui a deux paramètres:
Le premier paramètre spécifie le prototype de l'objet retourné.

Un deuxième paramètre facultatif est une collection de descripteurs de propriétés, similaires à ceux renvoyés par Object.getOwnPropertyDescriptors ().

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

Troisième cas d'utilisation: littéraux d'objets multiplateformes avec des prototypes arbitraires.

La meilleure façon syntaxiquement d'utiliser un littéral d'objet pour créer un objet avec un prototype arbitraire est d'utiliser la propriété spéciale __proto__:

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

Hélas, cette fonctionnalité est garantie d'être présente uniquement dans les navigateurs. La solution de contournement générale est Object.create () et l'affectation:

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

Mais vous pouvez également utiliser Object.getOwnPropertyDescriptors ():

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

Une autre alternative est Object.assign ():

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

Piège: copier des méthodes en utilisant super.

La méthode que super utilise est étroitement liée à son objet d'origine (l'objet dans lequel il est stocké). Il n'existe actuellement aucun moyen de copier ou déplacer une telle méthode vers un autre objet.

Voir Object.getOwnPropertyDescriptor dans la spécification officielle, ainsi que dans MDN Web Docs .

6. Virgules de fin


Les virgules suspendues (virgules de fin) - peuvent être utiles lors de l'ajout de nouveaux éléments, paramètres ou propriétés au code JavaScript. Si vous souhaitez ajouter une nouvelle propriété, vous ajoutez simplement une nouvelle ligne sans changer la précédente, si la virgule suspendue y est déjà utilisée. Cela rend les différences dans le contrôle de version plus propres et les changements de code peuvent être moins gênants.

Accrocher des virgules dans les littéraux


Tableaux

JavaScript ignore les virgules suspendues dans les tableaux:

 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 plusieurs points pendants sont utilisés, des trous seront créés. Un tableau avec des «trous» est appelé clairsemé (un tableau dense n'a pas de «trous»). Lors de l'itération d'un tableau en utilisant, par exemple, Array.prototype.forEach () ou Array.prototype.map (), les trous seront ignorés.

Les objets

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

Suspendre des virgules dans les fonctions


Définition des paramètres

Les définitions suivantes des paramètres de fonction sont valides et équivalentes les unes aux autres. Les virgules pendantes n'affectent pas la propriété length d'une fonction ou leur objet arguments.

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

Définition de la méthode

La virgule suspendue fonctionne également avec la définition de méthodes pour les classes ou les objets.

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

Appel de fonction

Les appels de fonction suivants sont valides et équivalents les uns aux autres.

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

Virgules suspendues non valides

La définition de paramètres de fonction ou l'appel d'une fonction contenant uniquement une virgule déclenche une SyntaxError. De plus, lors de l'utilisation des paramètres restants, les virgules suspendues ne sont pas autorisées.

 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 ',' 

Suspendre des virgules dans la déstructuration


Les virgules suspendues peuvent également être utilisées à gauche lors de l'utilisation d'une affectation destructrice.

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

Encore une fois, en utilisant les paramètres restants, une SyntaxError sera lancée.

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

JSON Dangling Commas


Les virgules suspendues dans un objet ne sont autorisées que dans ECMAScript 5. Étant donné que JSON est basé sur une syntaxe JavaScript antérieure à ES5, les virgules suspendues ne sont pas autorisées dans JSON.

Les deux lignes lancent une SyntaxError

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

Pourquoi les virgules suspendues sont-elles utiles?


Il y a deux avantages.

Tout d'abord, la réorganisation des éléments est plus facile car vous n'avez pas besoin d'ajouter ou de supprimer des virgules si le dernier élément change de position.

Deuxièmement, il aide les systèmes de contrôle de version à suivre ce qui a vraiment changé. Par exemple, à partir de:

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

fait que la ligne avec 'foo' et la ligne avec 'bar' sont marquées comme modifiées, bien que le seul vrai changement soit d'ajouter la dernière ligne.

Voir Virgules de fin dans MDN Web Docs .

7. SharedArrayBuffer


L'objet SharedArrayBuffer est utilisé pour créer un tampon fractionné de longueur fixe pour le stockage de données binaires primitives, similaire à l'objet ArrayBuffer, mais en revanche, les instances SharedArrayBuffer peuvent être utilisées pour créer une vue sur la mémoire partagée. SharedArrayBuffer ne peut pas être déconnecté.
 new SharedArrayBuffer(length) : Object 
length - La taille, en octets, pour créer le tableau de tampons.

return - Un nouvel objet SharedArrayBuffer de la longueur spécifiée. Son contenu après l'initialisation est 0.

Pour partager la mémoire à l'aide de l'objet SharedArrayBuffer entre un agent du cluster et un autre (l'agent peut être soit le programme principal de la page Web, soit l'un des travailleurs Web), postMessage et le clonage structuré sont utilisés.

L'algorithme de clonage structuré accepte SharedArrayBuffers et TypedArrays mappés sur SharedArrayBuffers. Dans les deux cas, l'objet SharedArrayBuffer est transmis au récepteur, ce qui crée un nouvel objet PrivateArrayBuffer privé à l'intérieur de l'agent récepteur (comme pour ArrayBuffer). Cependant, le bloc de données partagées référencé par les deux objets SharedArrayBuffer est le même bloc de données et les effets tiers dans le bloc dans l'un des agents deviendront éventuellement visibles dans l'autre agent.

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

La mémoire partagée peut être créée et modifiée simultanément dans les travailleurs ou le thread principal. Selon le système (CPU, OS, navigateur), cela peut prendre du temps jusqu'à ce que les modifications soient propagées à tous les contextes. Pour la synchronisation, des opérations atomiques sont nécessaires.

Shared Array Buffers est un bloc de construction primitif pour les abstractions de parallélisme de niveau supérieur. Ils vous permettent de partager les octets d'un objet SharedArrayBuffer entre plusieurs travailleurs et le thread principal (le tampon est partagé pour accéder aux octets, enveloppez-le dans un tableau typé). Ce type d'échange présente deux avantages:
Vous pouvez échanger des données entre les employés plus rapidement.

La coordination entre les travailleurs devient plus facile et plus rapide (par rapport à postMessage ()).

La mise en œuvre du travailleur est la suivante.

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

Tout d'abord, nous extrayons le tampon du tableau partagé qui nous a été envoyé, puis nous l'enveloppons dans un tableau typé afin de pouvoir l'utiliser localement.

Propriétés et méthodes de SharedArrayBuffer.

SharedArrayBuffer.length - La longueur du constructeur SharedArrayBuffer dont la valeur est 1.
SharedArrayBuffer.prototype - Autorise des propriétés supplémentaires pour tous les objets SharedArrayBuffer.

Instances SharedArrayBuffer
Les propriétés

SharedArrayBuffer.prototype.constructor - Définit une fonction qui crée un prototype d'un objet. La valeur initiale est le constructeur standard SharedArrayBuffer intégré.

SharedArrayBuffer.prototype.byteLength (lecture seule) - La taille du tableau en octets. Ce paramètre est défini lors de la création du tableau et ne peut pas être modifié.

Les méthodes

SharedArrayBuffer.prototype.slice () - Renvoie un nouveau SharedArrayBuffer dont le contenu est une copie des octets de ce SharedArrayBuffer depuis le début, y compris jusqu'à la fin, de l'exclusif. Si le début ou la fin est négatif, cela se réfère à l'index à la fin du tableau, pas au début. Cette méthode a le même algorithme 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 - L'index zéro auquel commence l'extraction. Vous pouvez utiliser un index négatif indiquant le décalage à partir de la fin de la séquence. slice (-2) extrait les deux derniers éléments d'une séquence. Si le début n'est pas défini, la tranche commence à l'index 0.
End - Index de base zéro auquel l'extraction doit être terminée.

Par exemple, la tranche (1,4) récupère le deuxième élément via le quatrième élément (éléments avec les indices 1, 2 et 3). Vous pouvez utiliser un index négatif indiquant le décalage à partir de la fin de la séquence. slice (2, -1) récupère le troisième élément via l'avant-dernier élément de la séquence. Si end est omis, tranche récupère jusqu'à la fin de la séquence (sab.byteLength).

Des exemples

 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 } 

Voir SharedArrayBuffer dans la spécification officielle, ainsi que dans les documents Web MDN .

8. Atomics


L'objet Atomics fournit des opérations atomiques en tant que méthodes statiques. Utilisé avec un objet SharedArrayBuffer.

Les opérations atomiques sont installées dans le module Atomics. Contrairement à d'autres objets globaux, Atomics n'est pas un constructeur. Il ne peut pas être utilisé avec le nouvel opérateur ou pour appeler un objet Atomics en tant que fonction. Toutes les propriétés et méthodes Atomics sont statiques (comme un objet Math, par exemple).

Lorsque la mémoire est partagée, plusieurs threads peuvent lire et écrire les mêmes données dans la mémoire. Les opérations atomiques garantissent que les valeurs attendues seront écrites et lues, et les opérations terminées avant que la prochaine opération ne commence son travail, et elles ne seront pas interrompues.

Les propriétés


Atomics [Symbol.toStringTag] - La valeur de cette propriété est Atomics.

Les méthodes


Opérations atomiques

  • Atomics.add () - Ajoute la valeur présentée à la valeur actuelle à la position spécifiée dans le tableau. Renvoie la valeur précédente à cette position.
  • Atomics.and () - Calcule un ET au niveau du bit à la position de tableau spécifiée. Renvoie la valeur précédente à cette position.
  • Atomics.compareExchange () - Enregistre la valeur présentée à la position spécifiée du tableau, si elle est équivalente à la valeur présentée. Renvoie la valeur précédente.
  • Atomics.exchange() — . .
  • Atomics.load() — .
  • Atomics.or() — OR . .
  • Atomics.store() — . .
  • Atomics.sub() — . .
  • Atomics.xor() — XOR . .

La méthode statique Atomics.add () ajoute la valeur à la valeur actuelle à la position spécifiée dans le tableau et renvoie la valeur précédente à cette position. Cette opération atomique garantit qu'aucune autre écriture ne se produit tant que la valeur modifiée n'est pas réécrite.

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

  • typedArray - Un tableau divisé d'entiers. Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array ou Uint32Array.
  • index - La position dans typedArray pour ajouter de la valeur.
  • value - Le nombre à ajouter.
  • return - La valeur précédente à la position spécifiée (typedArray [index]).

  • Lève une TypeError si le type typedArray n'est pas l'un des types entiers valides.
  • Lève une TypeError si le type typedArray n'est pas de type générique.
  • Lève RangeError si l'index est en dehors de typedArray.

Des exemples

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

Atomics.add () dans la spécification , dans MDN Web Docs .

Attendez et informez


Les méthodes wait () et wake () sont modélisées sur la base de futex («mutex fast user-space mutex») Linux et fournissent des moyens d'attendre un moment où un certain état ne devient pas vrai, et est généralement utilisé comme constructions de blocage.

Atomics.wait ()
Vérifie si la valeur qui est toujours représentée est contenue dans la position spécifiée du tableau et est en attente de temporisation. Renvoie ok, non égal ou expiré. Si l'attente n'est pas autorisée dans l'agent appelant, elle générera une erreur d'exception (la plupart des navigateurs n'autorisent pas wait () dans le flux principal du navigateur).

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

Problèmes d'optimisation


L'optimisation rend le code imprévisible chez les travailleurs. Dans les threads uniques, les compilateurs peuvent effectuer des optimisations qui cassent le code multithread.

Prenons, par exemple, le code suivant:

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

Dans un seul thread, la valeur de sharedArray [0] ne change jamais pendant l'exécution de la boucle (si sharedArray est un tableau ou un tableau typé qui n'a été fixé d'aucune façon). Par conséquent, le code peut être optimisé comme suit:

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

Cependant, en mode multithread, cette optimisation ne nous permet pas d'utiliser ce modèle pour attendre les modifications apportées dans un autre thread.

Un autre exemple est le code suivant:

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

Dans un thread, vous pouvez réorganiser ces opérations d'écriture car rien n'est lu entre elles. Plusieurs threads rencontrent des problèmes lorsque vous vous attendez à ce que les enregistrements soient effectués dans un ordre spécifique:

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

Ces types d'optimisation rendent presque impossible la synchronisation des actions de plusieurs travailleurs travaillant sur le même tampon avec un tableau commun.

Résolution des problèmes d'optimisation


Utilisation de la variable globale Atomics, dont les méthodes ont trois utilisations principales.

Premier cas d'utilisation: synchronisation.

Les méthodes atomiques peuvent être utilisées pour se synchroniser avec d'autres travailleurs. Par exemple, les deux opérations suivantes vous permettent de lire et d'écrire des données et ne sont jamais réorganisées par les compilateurs:

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

L'idée est d'utiliser des opérations ordinaires pour lire et écrire la plupart des données, tandis que les opérations Atomics (chargement, stockage et autres) garantissent la sécurité de la lecture et de l'écriture. Vous utiliserez souvent vos propres mécanismes de synchronisation, tels que les verrous, qui sont basés sur Atomics.

Voici un exemple très simple qui fonctionne toujours grâce à Atomics (j'ai sauté le paramètre sharedArray):

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

Deuxième cas d'utilisation: en attente de notification .

L'utilisation d'une boucle while pour attendre une notification n'est pas très efficace, donc Atomics a des opérations qui aident: Atomics.wait (Int32Array, index, valeur, timeout) et Atomics.wake (Int32Array, index, count).

Troisième cas d'utilisation: opérations atomiques
Certaines opérations Atomics font de l'arithmétique et ne peuvent pas être interrompues en même temps, ce qui facilite la synchronisation. Par exemple:

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

En gros, cette opération effectue: index + = valeur;

Problème avec des valeurs déchirées.

Un autre effet de problème avec la mémoire partagée est les valeurs déchirées (ordures): lors de la lecture, vous pouvez voir une valeur intermédiaire - ni la valeur avant l'écriture de la nouvelle valeur en mémoire, ni la nouvelle valeur.

La section Lectures sans déchirures de la spécification indique qu'il n'y a pas de lacunes si et seulement si:

  • La lecture et l'écriture s'effectuent via des tableaux typés (et non DataViews).
  • Les deux tableaux typés sont alignés avec leurs tampons de tableau partagé: sharedArray.byteOffset% sharedArray.BYTES_PER_ELEMENT === 0
  • Les deux tableaux typés ont le même nombre d'octets par élément.

En d'autres termes, les valeurs déchirées sont un problème lorsque le même tampon d'un tableau partagé est accessible via:

  • Un ou plusieurs DateViews;
  • Il existe un ou plusieurs tableaux typés non alignés;
  • Tableaux typés avec différentes tailles d'éléments;

Pour éviter un écart de valeurs dans ces cas, utilisez Atomics ou sync.

Tampons de baie partagée dans les utilisations


Tampons de tableau partagé et sémantique JavaScript pour exécuter une fonction en attente. JavaScript a la soi-disant sémantique d'exécution «avant la fin»: chaque fonction peut s'attendre à ce qu'elle ne soit pas interrompue par un autre thread tant qu'elle n'est pas terminée. Les fonctions deviennent des transactions et peuvent exécuter des algorithmes complets, tandis que personne ne voit les données avec lesquelles elles fonctionnent dans un état intermédiaire.

Les tampons de tableau partagé interrompent le cycle jusqu'à la fin (RTC): les données sur lesquelles la fonction travaille peuvent être modifiées par un autre thread pendant l'exécution de la fonction. Cependant, le code contrôle entièrement si cette violation RTC se produit: s'il n'utilise pas de tampons de baie partagée, il est sûr.

Ceci est à peu près similaire à la façon dont les fonctions asynchrones violent RTC. Là, vous activez l'opération de verrouillage à l'aide du mot-clé wait.

Les buffers de baies partagées permettent à emscripten de compiler des pthreads dans asm.js. Citant la page de documentation emscripten:

[Fr] [Les tampons de la baie partagée permettent] aux applications Emscripten de partager le tas de mémoire principal entre les travailleurs Web. Ceci, ainsi que les primitives pour la prise en charge atomique et futex de bas niveau, permet à Emscripten d'implémenter la prise en charge de l'API Pthreads (threads POSIX).

[Ru] [Les tampons de baies partagées permettent] aux applications Emscripten de partager un tas de mémoire principale entre les travailleurs Web. Avec les primitives atomiques de bas niveau et la prise en charge de futex, Emscripten permet la prise en charge de l'API Pthreads (threads POSIX).

Autrement dit, vous pouvez compiler du code C et C ++ multithread dans asm.js.

Des discussions sont en cours sur la meilleure façon d'utiliser le multithreading dans WebAssembly. Étant donné que les travailleurs Web sont relativement lourds, il est possible que WebAssembly introduise des threads légers. Vous pouvez également voir que les rubriques sont en route vers l'avenir de WebAssembly.

Échanger des données autres que des entiers


Actuellement, seuls les tableaux d'entiers (jusqu'à 32 bits de long) peuvent être utilisés. Cela signifie que la seule façon de partager d'autres types de données est de les coder sous forme d'entiers. Les outils qui peuvent vous aider comprennent:

  • TextEncoder et TextDecoder: le premier convertit les chaînes en instances Uint8Array, le second fait le contraire.
  • stringview.js: , . .
  • FlatJS: JavaScript (, ) (ArrayBuffer SharedArrayBuffer). JavaScript + FlatJS JavaScript. JavaScript (TypeScript . .) .
  • TurboScript: JavaScript- . asm.js WebAssembly.

En fin de compte, il est probable que des mécanismes supplémentaires - de niveau supérieur - pour l'échange de données apparaissent. Et les expériences continueront de déterminer à quoi devraient ressembler ces mécanismes.

Dans quelle mesure le code utilisant les tampons de la baie partagée fonctionne-t-il plus rapidement?

Lars T. Hansen a écrit deux implémentations de l'algorithme Mandelbrot (comme décrit dans son article « Un goût des nouvelles primitives parallèles de JavaScript », une version séquentielle et une version parallèle qui utilise plusieurs travailleurs Web. Jusqu'à 4 travailleurs Web et, par conséquent, cœurs de processeur, accélération augmente de façon presque linéaire, passant de 6,9 ​​images par seconde (1 travailleur Web) à 25,4 images par seconde (4 travailleurs Web). De plus, les travailleurs Web apportent des améliorations de productivité supplémentaires, mais plus modestes.

Hansen note que les accélérations sont impressionnantes, mais le travail parallèle est dû à un code plus complexe.

Informations supplémentaires sur les tampons de baies partagées et les technologies de prise en charge:


Autres technologies de concurrence JavaScript:


Voir Objet Atomics dans la spécification officielle , ainsi que dans les documents Web MDN .

9. Fonctions asynchrones


Création d'une fonction Async à l'aide du constructeur AsyncFunction


Le constructeur AsyncFunction crée un nouvel objet fonction async. En JavaScript, toute fonction asynchrone est en fait un objet AsyncFunction.

Notez que AsyncFunction n'est pas un objet global. Il peut être obtenu en exécutant le code suivant.

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

Syntaxe

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

arg1, arg2, ... argN - Noms utilisés par la fonction comme noms d'arguments formels. Chaque nom doit être une chaîne qui correspond à un identifiant JavaScript valide ou à une liste de ces chaînes séparées par des virgules; par exemple, «x», «theValue» ou «a, b».

functionBody - Une chaîne contenant la définition d'une fonction dans le code source JavaScript.

Les objets de fonction Async créés avec le constructeur AsyncFunction seront analysés au moment où la fonction est créée. Cela est moins efficace que de déclarer une fonction asynchrone à l'aide de l'expression de fonction asynchrone et de l'appeler dans votre code, car ces fonctions sont analysées avec le reste du code.

Tous les arguments passés à la fonction sont traités comme les noms des identificateurs de paramètre dans la fonction créée dans l'ordre dans lequel ils sont passés.

L'appel du constructeur AsyncFunction en tant que fonction (sans utiliser le nouvel opérateur) a le même effet que l'appel en tant que constructeur.

Les fonctions asynchrones créées à l'aide du constructeur AsyncFunction ne court-circuitent pas les contextes qui les créent; Ils sont toujours créés dans la portée globale. Au démarrage, ils ne pourront accéder qu'à leurs variables locales et globales, mais n'ont pas accès aux étendues dans lesquelles le constructeur AsyncFunction a été appelé. C'est différent de l'utilisation d'eval avec le code de la fonction asynchrone.

Exemple de création d'une fonction asynchrone à l'aide du constructeur 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  }); 

Déclaration de fonction asynchrone


La déclaration de fonction asynchrone définit une fonction asynchrone qui renvoie un objet AsyncFunction. Vous pouvez également définir des fonctions asynchrones à l'aide de l'expression de fonction asynchrone.

Syntaxe

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

name - Le nom de la fonction.
param - Le nom de l'argument à passer à la fonction.
instructions - Une expression contenant le corps de la fonction.

Après l'appel, la fonction async renvoie Promise. Une fois le résultat reçu, Promise est terminée et renvoie la valeur reçue. Lorsque la fonction async lève une exception, Promise échoue avec une valeur throws.

La fonction async peut contenir une expression d'attente qui suspend l'exécution de la fonction async et attend une réponse de la promesse passée, puis reprend la fonction async et renvoie la valeur reçue.

Le mot clé wait n'est valide que dans les fonctions asynchrones. Dans un autre contexte, vous obtiendrez une erreur SyntaxError.

Le but des fonctions async / attente est de simplifier l'utilisation des promesses de façon synchrone et de reproduire une action sur le groupe Promises. Tout comme les promesses sont comme des rappels structurés, async / wait est comme une combinaison de générateurs et de promesses.

Exemple

 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  }); 

À quoi sert l'asynchronie?


Vous pouvez écrire votre programme JS dans un fichier .js, mais votre code sera très probablement divisé en plusieurs parties. Et une seule des parties sera exécutée maintenant, et le reste sera exécuté plus tard. La fonction est la technique la plus utilisée pour diviser un programme en plusieurs parties.

Le principal problème de la plupart des développeurs qui voient JS pour la première fois est le manque de compréhension de ce qui ne se produira pas immédiatement après. En d'autres termes, les tâches qui ne peuvent pas être terminées maintenant, par définition, se termineront de manière asynchrone. Et nous n'aurons pas le comportement de blocage du programme que nous supposons. (You-Dont-Know-JS / async & performance, Jake Archibald).

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

Quelle est l'erreur ici? console.log () exécuté avant que nous ayons reçu les données de la demande.

La décision évidente d '«attendre» de maintenant à plus tard est d'utiliser des rappels:

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

Considérez diverses méthodes pour résoudre prématurément l'exécution de code synchrone.

Nous avons 3 fonctions 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); }); }); }); 

Dans cet exemple, il est difficile de ne pas remarquer la pyramide, qui augmente avec l'ajout de nouvelles fonctions. Ce style de codage est communément appelé Callback Hell . Il s'agit d'un certain schéma qui vous permet de contrôler les demandes concurrentes (asynchrones), ce qui garantit la séquence de leur exécution.

Une partie de la solution au problème des fonctions d'imbrication consiste à utiliser Promise (dont j'ai parlé dans mon dernier article , qui le supprime et rend le code plus propre. Ils fournissent également un moyen plus pratique de gérer les erreurs. Mais beaucoup n'aimaient pas cette syntaxe.

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

Les générateurs sont devenus une alternative à Promise (que j'ai également examinée dans un article précédent . Le générateur lui-même n'est pas adapté pour écrire du code asynchrone, mais si vous les utilisez avec Promise, nous obtenons quelque chose d'unique - du code asynchrone qui semble synchrone. En même temps, les générateurs fournissent le mécanisme de gestion des erreurs familier utilisant la construction try ... catch. Seuls les générateurs ont un gros inconvénient - pour les utiliser avec Promise, vous aurez besoin d'une fonction distincte qui contrôlera le processus du générateur A. Vous pouvez écrire cette fonction vous-même ou utiliser une bibliothèque tierce, par exemple co . Dans cet exemple, j'ai écrit mon implémentation d'une telle fonction.

 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(); }); } 

Chacune des méthodes de travail avec du code asynchrone a ses avantages et ses inconvénients.
Fonctions de rappel (fonctions Calback) - Facile à utiliser, mais avec une augmentation des fonctions imbriquées, la lisibilité commence à en souffrir.

Promesses (Promises) - Élégant et confortable, mais difficile à comprendre pour les débutants.

Générateurs (Generators) - Vous permettent d'écrire du code asynchrone de manière synchrone, mais ils nécessitent une fonction distincte, et le mécanisme de fonctionnement des générateurs est très compliqué.

Les fonctions asynchrones ont été créées sur la base des promesses et des générateurs, afin de rendre le travail avec le code asynchrone simple et compréhensible.

Afin de comprendre ce que sont les fonctions asynchrones, considérez l'exemple suivant:

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

Maintenant, si vous rendez la fonction asynchrone (en ajoutant le mot clé async), la fonction renverra une promesse qui contient un objet avec la propriété id.

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

Ainsi, nous pouvons dire que toute fonction asynchrone renvoie Promis (ou plutôt encapsule dans Promis la valeur qu'elle doit renvoyer). Si la valeur renvoyée à la fonction asynchrone est déjà une promesse, elle ne sera pas retournée à nouveau.

Pour obtenir la valeur de la promesse, nous pouvons utiliser la méthode then ().

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

Ou nous pouvons utiliser le mot-clé attendent, qui sera discuté plus tard.

Revenons à notre premier exemple (seulement cette fois, nous utiliserons la fonction réelle pour envoyer une requête HTTP.

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

Voici à quoi ressemble le code asynchrone en utilisant Promise.
Mais nous pouvons écrire du code asynchrone comme synchrone si nous utilisons des fonctions asynchrones.

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

La seule chose que je n'aime pas, c'est que l'opérateur asynchrone ne peut être utilisé que dans les fonctions asynchrones. Sinon, je n'aurais pas besoin d'utiliser la fonction main (). Bien sûr, vous pouvez également utiliser la méthode then (), mais le code ne sera plus comme asynchrone.

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

L'essentiel est que nous n'utilisons pas de fonctions de rappel pour obtenir des données de fetch (). Au lieu de cela, nous utilisons le mot clé wait, qui, pour ainsi dire, indique au runtime: attendez que la fonction fetch () s'exécute et écrive le résultat dans la variable de réponse. Et en utilisant la fonction de rappel, nous disons: attendez que la fonction fetch () s'exécute et appelez la fonction de rappel pour traiter les données.

Voici la différence évidente entre l'utilisation de la fonction Promise et async

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

L'opérateur wait ne peut être utilisé que dans le corps des fonctions asynchrones, et son action peut être utilisée sur n'importe quelle fonction qui renvoie une promesse.

Pour gérer les exceptions dans les fonctions asynchrones, il est habituel d'utiliser la construction 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 } } 

Et enfin ...

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

Voir Définitions des fonctions asynchrones dans la spécification officielle , ainsi que dans les documents Web MDN .

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


All Articles