Math.random()

Придумываем свой алгоритм ГПСЧ

Мы можем взять последовательность каких-то чисел и брать от них модуль числа. Самый простой пример, который приходит в голову. Нам нужно подумать, какую последовательность взять и модуль от чего. Если просто в лоб от 0 до N и модуль 2, то получится генератор 1 и 0:


Эта функция генерит нам последовательность 01010101010101… и назвать ее даже псевдослучайной никак нельзя. Чтобы генератор был случайным, он должен проходить тест на следующий бит. Но у нас не стоит такой задачи. Тем не менее даже без всяких тестов мы можем предсказать следующую последовательность, значит такой алгоритм в лоб не подходит, но мы в нужном направлении.

А что если взять какую-то известную, но нелинейную последовательность, например число PI. А в качестве значения для модуля будем брать не 2, а что-то другое. Можно даже подумать на тему меняющегося значения модуля. Последовательность цифр в числе Pi считается случайной. Генератор может работать, используя числа Пи, начиная с какой-то неизвестной точки. Пример такого алгоритма, с последовательностью на базе PI и с изменяемым модулем:

Но в JS число PI можно вывести только до 48 знака и не более. Поэтому предсказать такую последовательность все так же легко и каждый запуск такого генератора будет выдавать всегда одни и те же числа. Но наш генератор уже стал показывать числа от 0 до 9.

Мы получили генератор чисел от 0 до 9, но распределение очень неравномерное и каждый раз он будет генерировать одну и ту же последовательность.

Мы можем взять не число Pi, а время в числовом представлении и это число рассматривать как последовательность цифр, причем для того, чтобы каждый раз последовательность не повторялась, мы будем считывать ее с конца. Итого наш алгоритм нашего ГПСЧ будет выглядеть так:

Вот это уже похоже на генератор псевдослучайных чисел. И тот же Math.random() — это ГПСЧ, про него мы поговорим чуть позже. При этом у нас каждый раз первое число получается разным.

Собственно на этих простых примерах можно понять как работают более сложные генераторы случайных числе. И есть даже готовые алгоритмы. Для примера разберем один из них — это Линейный конгруэнтный ГПСЧ(LCPRNG).

Version notes

The random number sequence is the same as version 1.0 for string seeds.

  • Version 2.0 changed the sequence for non-string seeds.
  • Version 2.1 speeds seeding and uses window.crypto to autoseed if present.
  • Version 2.2 alters non-crypto autoseeding to sweep up entropy from plugins.
  • Version 2.3 adds support for «new», module loading, and a null seed arg.
  • Version 2.3.1 adds a build environment, module packaging, and tests.
  • Version 2.3.4 fixes bugs on IE8, and switches to MIT license.
  • Version 2.3.6 adds a readable options object argument.
  • Version 2.3.10 adds support for node.js crypto (contributed by ctd1500).
  • Version 2.3.11 adds an option to load and save internal state.
  • Version 2.4.0 adds implementations of several other fast PRNGs.
  • Version 2.4.2 adds an implementation of Baagoe’s very fast Alea PRNG.
  • Version 2.4.3 ignores nodejs crypto when under browserify.
  • Version 2.4.4 avoids strict mode problem with global this reference.
  • Version 3.0.1 removes Math.seedrandom for require(‘seedrandom’) users.
  • Version 3.0.3 updates package.json for CDN entrypoints.
  • Version 3.0.5 removes eval to avoid triggering content-security policy.

The standard ARC4 key scheduler cycles short keys, which means that seedrandom(‘ab’) is equivalent to seedrandom(‘abab’) and ‘ababab’. Therefore it is a good idea to add a terminator to avoid trivial equivalences on short string seeds, e.g., Math.seedrandom(str + ‘\0’). Starting with version 2.0, a terminator is added automatically for non-string seeds, so seeding with the number 111 is the same as seeding with ‘111\0’.

When seedrandom() is called with zero args or a null seed, it uses a seed drawn from the browser crypto object if present. If there is no crypto support, seedrandom() uses the current time, the native rng, and a walk of several DOM objects to collect a few bits of entropy.

Each time the one- or two-argument forms of seedrandom are called, entropy from the passed seed is accumulated in a pool to help generate future seeds for the zero- and two-argument forms of seedrandom.

On speed — This javascript implementation of Math.random() is several times slower than the built-in Math.random() because it is not native code, but that is typically fast enough. Some details (timings on Chrome 25 on a 2010 vintage macbook):

  • seeded Math.random() — avg less than 0.0002 milliseconds per call
  • seedrandom(‘explicit.’) — avg less than 0.2 milliseconds per call
  • seedrandom(‘explicit.’, true) — avg less than 0.2 milliseconds per call
  • seedrandom() with crypto — avg less than 0.2 milliseconds per call

Autoseeding without crypto is somewhat slow, about 20-30 milliseconds on a 2012 windows 7 1.5ghz i5 laptop, as seen on Firefox 19, IE 10, and Opera. Seeded rng calls themselves are fast across these browsers, with slowest numbers on Opera at about 0.0005 ms per seeded Math.random().

Создание генератора

Для объявления генератора используется новая синтаксическая конструкция: (функция со звёздочкой).

Её называют «функция-генератор» (generator function).

Выглядит это так:

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

Правильнее всего будет воспринимать генератор как «замороженный вызов функции»:

При создании генератора код находится в начале своего выполнения.

Основным методом генератора является . При вызове он возобновляет выполнение кода до ближайшего ключевого слова . По достижении выполнение приостанавливается, а значение – возвращается во внешний код:

Повторный вызов возобновит выполнение и вернёт результат следующего :

И, наконец, последний вызов завершит выполнение функции и вернёт результат :

Функция завершена. Внешний код должен увидеть это из свойства и обработать , как окончательный результат.

Новые вызовы больше не имеют смысла. Впрочем, если они и будут, то не вызовут ошибки, но будут возвращать один и тот же объект: .

«Открутить назад» завершившийся генератор нельзя, но можно создать новый ещё одним вызовом и выполнить его.

или ?

Можно ставить звёздочку как сразу после , так и позже, перед названием. В интернете можно найти обе эти формы записи, они верны:

Технически, нет разницы, но писать то так то эдак – довольно странно, надо остановиться на чём-то одном.

Автор этого текста полагает, что правильнее использовать первый вариант , так как звёздочка относится к типу объявляемой сущности ( – «функция-генератор»), а не к её названию. Конечно, это всего лишь рекомендация-мнение, не обязательное к выполнению, работать будет в любом случае.

Перебор генераторов

Как вы, наверное, уже догадались по наличию метода , генераторы являются перебираемыми объектами.

Возвращаемые ими значения можно перебирать через :

Выглядит гораздо красивее, чем использование , верно?

…Но обратите внимание: пример выше выводит значение , затем. Значение выведено не будет!. Это из-за того, что перебор через игнорирует последнее значение, при котором

Поэтому, если мы хотим, чтобы были все значения при переборе через , то надо возвращать их через :

Это из-за того, что перебор через игнорирует последнее значение, при котором . Поэтому, если мы хотим, чтобы были все значения при переборе через , то надо возвращать их через :


Так как генераторы являются перебираемыми объектами, мы можем использовать всю связанную с ними функциональность, например оператор расширения :

В коде выше превращает перебираемый объект-генератор в массив элементов (подробнее ознакомиться с оператором расширения можно в главе )

Правильная рандомная (случайная) функция

Как видно из приведенных выше примеров, было бы неплохо создать правильную рандомную (случайную) функцию, которая будет использоваться для всех рандомных (случайных) целочисленных целей.

Эта функция JavaScript всегда возвращает случайное число между min (включенным) и max (исключенным):

Пример

function getRndInteger(min, max) {   return Math.floor(Math.random() * (max — min) ) + min; }

Эта функция JavaScript всегда возвращает случайное число между min и max (оба включены):

Пример

function getRndInteger(min, max) {   return Math.floor(Math.random() * (max — min + 1) ) + min; }

❮ Prev Next ❯

Пожалуйста, включите JavaScript для просмотра комментариев, предоставленных Disqus.

Tweaking numbers so they «look random»

I agree with Phil H that humans are so good at finding patterns that they often think they see patterns even in «perfectly random» sequences of numbers (clustering illusion, apophenia, gambler’s fallacy, etc).

Plots of true random positions generally have lots of clumps and points that «coincidentally» fall very close together, which looks pretty suspicious.

Artists often take completely randomly generated patterns and «nudge» them to make them appear «more random», even though that careful nudging actually makes the pattern less random (a), (b), (c), (d), etc.

Alternatively, a low-discrepancy sequence sometimes «looks better» than a true random sequence and is much faster to generate.

LICENSE (MIT)

Copyright 2019 David Bau.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the «Software»), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED «AS IS», WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Other Fast PRNG Algorithms

The package includes some other fast PRNGs. To use Johannes Baagøe’s extremely fast Alea PRNG:

<script src="//cdnjs.cloudflare.com/ajax/libs/seedrandom/3.0.5/lib/alea.min.js">
</script>
// Use alea for Johannes Baagøe's clever and fast floating-point RNG.
var arng = new alea('hello.');

// By default provides 32 bits of randomness in a float.
console.log(arng());               // Always 0.4783254903741181

// Use "double" to get 56 bits of randomness.
console.log(arng.double());        // Always 0.8297006866124559

// Use "int32" to get a 32 bit (signed) integer.
console.log(arng.int32());         // Always 1076136327

Besides alea, there are several other faster PRNGs available. Note that none of these fast PRNGs provide autoseeding: you need to provide your own seed (or use the default autoseeded seedrandom to make a seed).

PRNG name Time vs native Period Author
1.95 ns, 0.9x ~2^116 Baagøe
2.04 ns, 0.9x 2^128-1 Marsaglia
2.32 ns, 1.1x ~2^127 Neves/Araujo (ChaCha)
2.40 ns, 1.1x 2^192-2^32 Marsaglia
2.40 ns, 1.1x 2^4096-2^32 Brent (xorgens)
2.64 ns, 1.3x 2^256-1 Panneton/L’ecuyer
3.80 ns, 1.8x ~2^1600 Bau (ARC4)

(Timings were done on node v0.12.2 on a single-core Google Compute Engine instance. is just the 32-bit version of the RC4-based PRNG originally packaged with seedrandom.)

JS Tutorial

JS HOMEJS IntroductionJS Where ToJS OutputJS StatementsJS SyntaxJS CommentsJS VariablesJS OperatorsJS ArithmeticJS AssignmentJS Data TypesJS FunctionsJS ObjectsJS EventsJS StringsJS String MethodsJS NumbersJS Number MethodsJS ArraysJS Array MethodsJS Array SortJS Array IterationJS DatesJS Date FormatsJS Date Get MethodsJS Date Set MethodsJS MathJS RandomJS BooleansJS ComparisonsJS ConditionsJS SwitchJS Loop ForJS Loop WhileJS BreakJS Type ConversionJS BitwiseJS RegExpJS ErrorsJS ScopeJS HoistingJS Strict ModeJS this KeywordJS LetJS ConstJS Arrow FunctionJS DebuggingJS Style GuideJS Best PracticesJS MistakesJS PerformanceJS Reserved WordsJS VersionsJS Version ES5JS Version ES6JS JSON

Why is this needed?

Despite being capable of producing numbers within [0, 1), there are a few downsides to doing so:

  • It is inconsistent between engines as to how many bits of randomness:
    • Internet Explorer: 53 bits
    • Mozilla Firefox: 53 bits
    • Google Chrome/node.js: 32 bits
    • Apple Safari: 32 bits
  • It is non-deterministic, which means you can’t replay results consistently
  • In older browsers, there can be manipulation through cross-frame random polling. This is mostly fixed in newer browsers and is required to be fixed in ECMAScript 6.

Also, and most crucially, most developers tend to use improper and biased logic as to generating integers within a uniform distribution.

Сумісність із браузерами

Таблиця сумісності на цій сторінці сформована автоматично із структурованих даних. Якщо ви маєте бажання зробити свій внесок до цих даних — будь-ласка, ось репозиторій https://github.com/mdn/browser-compat-data, надішліть нам свій pull request.

Update compatibility data on GitHub

Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Firefox for Android Opera for Android Safari on iOS Samsung Internet Node.js
Chrome Full support 1 Edge Full support 12 Firefox Full support 1 IE Full support 3 Opera Full support 3 Safari Full support 1 WebView Android Full support 1 Chrome Android Full support 18 Firefox Android Full support 4 Opera Android Full support 10.1 Safari iOS Full support 1 Samsung Internet Android Full support 1.0 nodejs Full support 0.1.100

Alternate API

There is an alternate API which may be easier to use, but may be less performant. In scenarios where performance is paramount, it is recommended to use the aforementioned API.

constrandom=newRandom(MersenneTwister19937.seedWithArray(0x12345678,0x90abcdef));constvalue=r.integer(,99);constotherRandom=newRandom();

This abstracts the concepts of engines and distributions.

  • : Produce an integer within the inclusive range . can be at its minimum -9007199254740992 (2 ** 53). can be at its maximum 9007199254740992 (2 ** 53). The special number is never returned.
  • : Produce a floating point number within the range . Uses 53 bits of randomness.
  • : Produce a boolean with a 50% chance of it being .
  • : Produce a boolean with the specified chance causing it to be .
  • : Produce a boolean with / chance of it being true.
  • : Return a random value within the provided within the sliced bounds of and .
  • : Shuffle the provided (in-place). Similar to .
  • : From the array, produce an array with elements that are randomly chosen without repeats.
  • : Same as
  • : Produce an array of length with as many rolls.
  • : Produce a random string using numbers, uppercase and lowercase letters, , and of length .
  • : Produce a random string using the provided string as the possible characters to choose from of length .
  • or : Produce a random string comprised of numbers or the characters of length .
  • : Produce a random string comprised of numbers or the characters of length .
  • : Produce a random within the inclusive range of . and must both be s.

Cryptographic random number techniques compatible with ancient and modern browsers

One way to generate true random numbers in JavaScript is to capture mouse events and add them into a pool of entropy, keeping track of some (hopefully conservative) estimate of the entropy added. Once the pool is «full» (estimates indicate that at least 128 bits of entropy have been added), use some cryptographically secure random number generator to generate random numbers from the pool — typically by using a one-way hash so that a sequence of a few thousand output numbers are not enough to deduce the state of the entropy pool and hence predict the next output number.

One implementation: http://lightsecond.com/passphrase.html

Примеры

Пример: использование метода



// Вернёт значение 20 x = Math.round(20.49);

// Вернёт значение 21 x = Math.round(20.5);

// Вернёт значение -20 x = Math.round(-20.5);

// Вернёт значение -21 x = Math.round(-20.51);

// Вернёт значение 1 (!) // Обратите внимание на ошибку округления из-за неточности арифметики с плавающей запятой // Сравните этот результат с результатом Math.round(1.005, -2) из следующего примера x = Math.round(1.005*100)/100;

Пример: корректировка округления десятичных дробей

// Замыкание
(function() {
  /**
   * Корректировка округления десятичных дробей.
   *
   * @param {String}  type  Тип корректировки.
   * @param {Number}  value Число.
   * @param {Integer} exp   Показатель степени (десятичный логарифм основания корректировки).
   * @returns {Number} Скорректированное значение.
   */
  function decimalAdjust(type, value, exp) {
    // Если степень не определена, либо равна нулю...
    if (typeof exp === 'undefined' || +exp === 0) {
      return Math(value);
    }
    value = +value;
    exp = +exp;
    // Если значение не является числом, либо степень не является целым числом...
    if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
      return NaN;
    }
    // Сдвиг разрядов
    value = value.toString().split('e');
    value = Math(+(value + 'e' + (value ? (+value - exp) : -exp)));
    // Обратный сдвиг
    value = value.toString().split('e');
    return +(value + 'e' + (value ? (+value + exp) : exp));
  }

  // Десятичное округление к ближайшему
  if (!Math.round10) {
    Math.round10 = function(value, exp) {
      return decimalAdjust('round', value, exp);
    };
  }
  // Десятичное округление вниз
  if (!Math.floor10) {
    Math.floor10 = function(value, exp) {
      return decimalAdjust('floor', value, exp);
    };
  }
  // Десятичное округление вверх
  if (!Math.ceil10) {
    Math.ceil10 = function(value, exp) {
      return decimalAdjust('ceil', value, exp);
    };
  }
})();

// Округление к ближайшему
Math.round10(55.55, -1);   // 55.6
Math.round10(55.549, -1);  // 55.5
Math.round10(55, 1);       // 60
Math.round10(54.9, 1);     // 50
Math.round10(-55.55, -1);  // -55.5
Math.round10(-55.551, -1); // -55.6
Math.round10(-55, 1);      // -50
Math.round10(-55.1, 1);    // -60
Math.round10(1.005, -2);   // 1.01 -- сравните этот результат с результатом Math.round(1.005*100)/100 выше
// Округление вниз
Math.floor10(55.59, -1);   // 55.5
Math.floor10(59, 1);       // 50
Math.floor10(-55.51, -1);  // -55.6
Math.floor10(-51, 1);      // -60
// Округление вверх
Math.ceil10(55.51, -1);    // 55.6
Math.ceil10(51, 1);        // 60
Math.ceil10(-55.59, -1);   // -55.5
Math.ceil10(-59, 1);       // -50

yield – дорога в обе стороны

До этого генераторы наиболее напоминали «итераторы на стероидах». Но, как мы сейчас увидим, это не так, есть фундаментальное различие, генераторы гораздо мощнее и гибче.

Всё дело в том, что – дорога в обе стороны: он не только возвращает результат наружу, но и может передавать значение извне в генератор.

Вызов делает следующее:

  • Возвращает во внешний код, приостанавливая выполнение генератора.
  • Внешний код может обработать значение, и затем вызвать с аргументом: .
  • Генератор продолжит выполнение, аргумент будет возвращён как результат (и записан в ).

Продемонстрируем это на примере:

На рисунке ниже прямоугольником изображён генератор, а вокруг него – «внешний код», который с ним взаимодействует:

На этой иллюстрации показано то, что происходит в генераторе:

  1. Первый вызов – всегда без аргумента, он начинает выполнение и возвращает результат первого («2+2?»). На этой точке генератор приостанавливает выполнение.
  2. Результат переходит во внешний код (в ). Внешний код может выполнять любые асинхронные задачи, генератор стоит «на паузе».
  3. Когда асинхронные задачи готовы, внешний код вызывает с аргументом. Выполнение генератора возобновляется, а выходит из присваивания как результат .

В примере выше – только два .

Увеличим их количество, чтобы стал более понятен общий поток выполнения:

Взаимодействие с внешним кодом:


  1. Первый начинает выполнение… Оно доходит до первого .
  2. Результат возвращается во внешний код.
  3. Второй передаёт обратно в генератор как результат первого и возобновляет выполнение.
  4. …Оно доходит до второго , который станет результатом .
  5. Третий передаёт в генератор как результат второго и возобновляет выполнение, которое завершается окончанием функции, так что .

Получается «пинг-понг»: каждый передаёт в генератор значение, которое становится результатом текущего , возобновляет выполнение и получает выражение из следующего . Исключением является первый вызов , который не может передать значение в генератор, т.к. ещё не было ни одного .

Пример использования

Базовое использование

В следующем примере с помощью метода random() мы получим случайное число от 0 (включительно) до 1 (не включая):

const rnd = Math.random();
console.log(rnd); // возвращаемое значение 0.5017845092137254

Получение случайного числа в заданном диапазоне

В следующем примере с помощью метода random() мы рассмотрим как получить случайное число внутри определенного диапазона

Обратите внимание, что возвращаемое значение не может быть меньше параметра min и не более, или равно параметра max:

function getRandomFromRange(min, max) {
  return Math.random() * (max - min) + min;
}

console.log(getRandomFromRange(5, 10)); // возвращаемое значение 6.830906542874363
console.log(getRandomFromRange(5, 10)); // возвращаемое значение 9.436449613234068
console.log(getRandomFromRange(5, 10)); // возвращаемое значение 6.4493344451274055
console.log(getRandomFromRange(5, 10)); // возвращаемое значение 5.160973635403946
console.log(getRandomFromRange(5, 10)); // возвращаемое значение 5.369261822513969

Получение случайного целого числа в заданном диапазоне

В следующем примере мы рассмотрим как с помощью метода random(), ceil() и floor() объекта Math получить случайное целое число внутри определенного диапазона

Обратите внимание, что возвращаемое значение не может быть меньше параметра min и не более, или равно параметра max:

function getRandomIntFromRange(min, max) {
  min = Math.ceil(min); // вычисляет и возвращает наименьшее целое число, которое больше или равно переданному числу (округляет число вверх)
  max = Math.floor(max); // вычисляет и возвращает наибольшее целое число, которое меньше или равно переданному числу (округляет число вниз)
  return Math.floor(Math.random() * (max - min)) + min; 
}

console.log(getRandomIntFromRange(5, 10)); // возвращаемое значение 6
console.log(getRandomIntFromRange(5, 10)); // возвращаемое значение 9
console.log(getRandomIntFromRange(5, 10)); // возвращаемое значение 1
console.log(getRandomIntFromRange(5, 10)); // возвращаемое значение 5
console.log(getRandomIntFromRange(5, 10)); // возвращаемое значение 6

Получение случайного элемента в массиве

В следующем примере мы рассмотрим как с помощью методов ceil() и random() найти случайный элемент внутри массива:

const arr = ;

const randomElement = arr[Math.floor(Math.random() * arr.length)];

Например:
arr; // индекс будет соответствовать 2

JavaScript Math

JS Уроки

JS HOMEJS IntroductionJS Where ToJS OutputJS StatementsJS SyntaxJS CommentsJS VariablesJS OperatorsJS ArithmeticJS AssignmentJS Data TypesJS FunctionsJS ObjectsJS ScopeJS EventsJS StringsJS String MethodsJS NumbersJS Number MethodsJS ArraysJS Array MethodsJS Array SortJS Array IterationJS DatesJS Date FormatsJS Date Get MethodsJS Date Set MethodsJS MathJS RandomJS BooleansJS ComparisonsJS ConditionsJS SwitchJS Loop ForJS Loop WhileJS BreakJS Type ConversionJS BitwiseJS RegExpJS ErrorsJS DebuggingJS HoistingJS Strict ModeJS this KeywordJS Style GuideJS Best PracticesJS MistakesJS PerformanceJS Reserved WordsJS VersionsJS Version ES5JS Version ES6JS JSON

Библиотека «co»

Библиотека , как и в примере выше, получает генератор и выполняет его.

Начнём сразу с примера, а потом – детали и полезные возможности:

Предполагается, что библиотека подключена к странице , например, отсюда: http://cdnjs.com/libraries/co/. В примере выше делает промиса с , который через секунду возвращает .

Вызов возвращает промис с результатом генератора. Если в примере выше что-то возвратит, то это можно будет получить через в результате :

Обязательно нужен

Частая ошибка начинающих – вообще забывать про обработку результата . Даже если результата нет, ошибки нужно обработать через , иначе они «подвиснут» в промисе.

Такой код ничего не выведет:

Программист даже не узнает об ошибке. Особенно обидно, когда это опечатка или другая программная ошибка, которую обязательно нужно поправить.

Правильный вариант:

Большинство примеров этого не содержат, но это лишь потому, что в примерах ошибок нет. А в реальном коде обязательно нужен .

Библиотека умеет выполнять не только промисы. Есть несколько видов значений, которые можно ить, и их обработает :

  • Промис.
  • Объект-генератор.
  • Функция-генератор – её выполнит, затем выполнит полученный генератор.
  • Функция с единственным аргументом вида – библиотека её запустит со своей функцией- и будет ожидать, что при ошибке она вызовет , а при успешном выполнении – . То есть, в первом аргументе – будет ошибка (если есть), а втором – результат (если нет ошибки). После чего результат будет передан в генератор.
  • Массив или объект из вышеперечисленного. При этом все задачи будут выполнены параллельно, и результат, в той же структуре, будет выдан наружу.

В примере ниже происходит всех этих видов значений. Библиотека обеспечивает их выполнение и возврат результата в генератор:

Устаревший

Отдельно заметим вариант с . Такие функции, с единственным-аргументом callback’ом, в англоязычной литературе называют «thunk».

Функция обязана выполниться и вызвать (асинхронно) либо с ошибкой, либо с результатом.

Использование таких функций в является устаревшим подходом, так как там, где можно использовать , можно использовать и промисы. При этом промисы мощнее. Но в старом коде его ещё можно встретить.

Посмотрим пример посложнее, с композицией генераторов:

Это – отличный вариант для библиотеки . Композиция вызывает в потоке внешнего генератора. Аналогично делает и .

Поэтому из строки в попадает напрямую в библиотеку , как если бы он был сделан так:

Пример можно переписать с использованием вот так:

Заметим, что для перехвата ошибок при получении аватара используется вокруг :

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

Использование генераторов для перебираемых объектов

Некоторое время назад, в главе Перебираемые объекты, мы создали перебираемый объект , который возвращает значения .

Давайте вспомним код:

Мы можем использовать функцию-генератор для итерации, указав её в .

Вот тот же , но с гораздо более компактным итератором:

Это работает, потому что теперь возвращает генератор, и его методы – в точности то, что ожидает :

  • у него есть метод
  • который возвращает значения в виде

Это не совпадение, конечно. Генераторы были добавлены в язык JavaScript, в частности, с целью упростить создание перебираемых объектов.


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

Генераторы могут генерировать бесконечно

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

Конечно, нам потребуется (или ) в цикле по такому генератору, иначе цикл будет продолжаться бесконечно, и скрипт «зависнет».

CJS / Node.js usage

// Local PRNG: does not affect Math.random.
var seedrandom = require('seedrandom');
var rng = seedrandom('hello.');
console.log(rng());                  // Always 0.9282578795792454

// Global PRNG: set Math.random.
seedrandom('hello.', { global: true });
console.log(Math.random());          // Always 0.9282578795792454

// Autoseeded ARC4-based PRNG.
rng = seedrandom();
console.log(rng());                  // Reasonably unpredictable.

// Mixing accumulated entropy.
rng = seedrandom('added entropy.', { entropy: true });
console.log(rng());                  // As unpredictable as added entropy.

// Using alternate algorithms, as listed above.
var rng2 = seedrandom.xor4096('hello.')
console.log(rng2());

Starting in version 3, when using via require(‘seedrandom’), the global is no longer available.

Композиция генераторов

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

Разберём композицию на примере.

Пусть у нас есть функция , которая генерирует последовательность чисел:

Мы хотим на её основе сделать другую функцию , которая будет генерировать коды для буквенно-цифровых символов латинского алфавита:

  • – для
  • – для
  • – для

Далее этот набор кодов можно превратить в строку и использовать, к примеру, для выбора из него случайного пароля. Только символы пунктуации ещё хорошо бы добавить для надёжности, но в этом примере мы будем без них.

Естественно, раз в нашем распоряжении есть готовый генератор , то хорошо бы его использовать.

Конечно, можно внутри запустить несколько раз , объединить результаты и вернуть. Так мы бы сделали с обычными функциями. Но композиция – это кое-что получше.

Она выглядит так:

Здесь использована специальная форма . Она применима только к другому генератору и делегирует ему выполнение.

То есть, при интерпретатор переходит внутрь генератора-аргумента, к примеру, , выполняет его, и все , которые он делает, выходят из внешнего генератора.

Получается – как будто мы вставили код внутреннего генератора во внешний напрямую, вот так:

Код выше по поведению полностью идентичен варианту с . При этом, конечно, переменные вложенного генератора не попадают во внешний, «делегирование» только выводит результаты во внешний поток.

Композиция – это естественное встраивание одного генератора в поток другого. При композиции значения из вложенного генератора выдаются «по мере готовности». Поэтому она будет работать даже если поток данных из вложенного генератора оказался бесконечным или ожидает какого-либо условия для завершения.

JavaScript

JS Array concat() constructor copyWithin() entries() every() fill() filter() find() findIndex() forEach() from() includes() indexOf() isArray() join() keys() length lastIndexOf() map() pop() prototype push() reduce() reduceRight() reverse() shift() slice() some() sort() splice() toString() unshift() valueOf()

JS Boolean constructor prototype toString() valueOf()

JS Classes constructor() extends static super

JS Date constructor getDate() getDay() getFullYear() getHours() getMilliseconds() getMinutes() getMonth() getSeconds() getTime() getTimezoneOffset() getUTCDate() getUTCDay() getUTCFullYear() getUTCHours() getUTCMilliseconds() getUTCMinutes() getUTCMonth() getUTCSeconds() now() parse() prototype setDate() setFullYear() setHours() setMilliseconds() setMinutes() setMonth() setSeconds() setTime() setUTCDate() setUTCFullYear() setUTCHours() setUTCMilliseconds() setUTCMinutes() setUTCMonth() setUTCSeconds() toDateString() toISOString() toJSON() toLocaleDateString() toLocaleTimeString() toLocaleString() toString() toTimeString() toUTCString() UTC() valueOf()

JS Error name message

JS Global decodeURI() decodeURIComponent() encodeURI() encodeURIComponent() escape() eval() Infinity isFinite() isNaN() NaN Number() parseFloat() parseInt() String() undefined unescape()

JS JSON parse() stringify()

JS Math abs() acos() acosh() asin() asinh() atan() atan2() atanh() cbrt() ceil() cos() cosh() E exp() floor() LN2 LN10 log() LOG2E LOG10E max() min() PI pow() random() round() sin() sqrt() SQRT1_2 SQRT2 tan() tanh() trunc()

JS Number constructor isFinite() isInteger() isNaN() isSafeInteger() MAX_VALUE MIN_VALUE NEGATIVE_INFINITY NaN POSITIVE_INFINITY prototype toExponential() toFixed() toLocaleString() toPrecision() toString() valueOf()

JS OperatorsJS RegExp constructor compile() exec() g global i ignoreCase lastIndex m multiline n+ n* n? n{X} n{X,Y} n{X,} n$ ^n ?=n ?!n source test() toString() (x|y) . \w \W \d \D \s \S \b \B \0 \n \f \r \t \v \xxx \xdd \uxxxx

JS Statements break class continue debugger do…while for for…in for…of function if…else return switch throw try…catch var while

JS String charAt() charCodeAt() concat() constructor endsWith() fromCharCode() includes() indexOf() lastIndexOf() length localeCompare() match() prototype repeat() replace() search() slice() split() startsWith() substr() substring() toLocaleLowerCase() toLocaleUpperCase() toLowerCase() toString() toUpperCase() trim() valueOf()

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

Для объявления генератора используется специальная синтаксическая конструкция: , которая называется «функция-генератор».

Выглядит она так:

Функции-генераторы ведут себя не так, как обычные. Когда такая функция вызвана, она не выполняет свой код. Вместо этого она возвращает специальный объект, так называемый «генератор», для управления её выполнением.

Вот, посмотрите:

Выполнение кода функции ещё не началось:

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

Результатом метода всегда является объект с двумя свойствами:

  • : значение из .
  • : , если выполнение функции завершено, иначе .

Например, здесь мы создаём генератор и получаем первое из возвращаемых им значений:

На данный момент мы получили только первое значение, выполнение функции остановлено на второй строке:

Повторный вызов возобновит выполнение кода и вернёт результат следующего :

И, наконец, последний вызов завершит выполнение функции и вернёт результат :

Сейчас генератор полностью выполнен. Мы можем увидеть это по свойству и обработать как окончательный результат.

Новые вызовы больше не имеют смысла. Впрочем, если они и будут, то не вызовут ошибки, но будут возвращать один и тот же объект: .

или ?

Нет разницы, оба синтаксиса корректны.

Но обычно предпочтителен первый вариант, так как звёздочка относится к типу объявляемой сущности ( – «функция-генератор»), а не к её названию, так что резонно расположить её у слова .

JS Tutorial

JS HOMEJS IntroductionJS Where ToJS OutputJS StatementsJS SyntaxJS CommentsJS VariablesJS OperatorsJS ArithmeticJS AssignmentJS Data TypesJS FunctionsJS ObjectsJS EventsJS StringsJS String MethodsJS NumbersJS Number MethodsJS ArraysJS Array MethodsJS Array SortJS Array IterationJS DatesJS Date FormatsJS Date Get MethodsJS Date Set MethodsJS MathJS RandomJS BooleansJS ComparisonsJS ConditionsJS SwitchJS Loop ForJS Loop WhileJS BreakJS Type ConversionJS BitwiseJS RegExpJS ErrorsJS ScopeJS HoistingJS Strict ModeJS this KeywordJS LetJS ConstJS Arrow FunctionJS DebuggingJS Style GuideJS Best PracticesJS MistakesJS PerformanceJS Reserved WordsJS VersionsJS Version ES5JS Version ES6JS JSON


С этим читают