├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── .gitignore ├── JavaScript ├── theory │ ├── reveal.json │ └── presentation.md ├── e-spread.js ├── g-yield-star.js ├── helpers │ ├── do-notation.js │ ├── maybe.js │ ├── get-json.js │ └── server.js ├── h-yield-gen.js ├── d-for-of.js ├── c-return.js ├── b-yield.js ├── 6-do-maybe.js ├── f-next.js ├── 7-do-promise.js ├── 8-catch-promise.js ├── i-return.js ├── 2-range-generator.js ├── 3-primes.js ├── 9-maybe-catch.js ├── 5-producer-consumer-send.js ├── j-throw.js ├── 4-producer-consumer.js ├── 1-range.js └── a-generator.js ├── eslint.config.js ├── README.md ├── Exercises.ru.md ├── prettier.config.js ├── .editorconfig ├── knowledge.map ├── Exercises ├── 1-ids.js └── 1-ids.test ├── package.json ├── Python ├── 2-generator.py ├── 3-primes.py └── 1-range.py └── LICENSE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: tshemsedinov 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /JavaScript/theory/reveal.json: -------------------------------------------------------------------------------- 1 | { 2 | "transition": "none" 3 | } 4 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const init = require('eslint-config-metarhia'); 4 | 5 | module.exports = init; 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Generators 2 | 3 | [![Генераторы и асинхронные генераторы в JavaScript](https://img.youtube.com/vi/kvNm9D32s8s/0.jpg)](https://www.youtube.com/watch?v=kvNm9D32s8s) 4 | -------------------------------------------------------------------------------- /Exercises.ru.md: -------------------------------------------------------------------------------- 1 | ## Генераторы 2 | 3 | 1. Оптимизируйте генератор идентификаторов `function* ids()` генерирующий такую 4 | же последовательность, как и уже реализованный в примере, но без массива `free`. 5 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | printWidth: 80, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | tabWidth: 2, 8 | useTabs: false, 9 | semi: true, 10 | }; 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.js,*.mjs,*.ts,*.json,*.yml}] 11 | indent_size = 2 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /JavaScript/e-spread.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* ids(...args) { 4 | let i = 0; 5 | while (args.length > i) { 6 | const id = args[i++]; 7 | if (id === undefined) return; 8 | yield id; 9 | } 10 | } 11 | 12 | const id = ids(1011, 1078, 1292, 1731, undefined, 1501, 1550); 13 | console.log(...id); 14 | -------------------------------------------------------------------------------- /knowledge.map: -------------------------------------------------------------------------------- 1 | { 2 | description: 'Генератор - это функция, являющаяся абстракцией итерирования с остановкой и продолжением исполнения в середине функции, возвращающая одно значение за каждый вызов и позволяющая вычислять последовательности, которые невозможно вычислить до конца', 3 | dependencies: ['Function', 'Iteration'] 4 | } -------------------------------------------------------------------------------- /JavaScript/g-yield-star.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* genFn() { 4 | yield* [10, 20, 30]; 5 | //yield* new Set([10, 20, 30]); 6 | } 7 | 8 | const c = genFn(); 9 | const val1 = c.next(); 10 | const val2 = c.next(); 11 | const val3 = c.next(); 12 | const val4 = c.next(); 13 | console.log({ c, val1, val2, val3, val4 }); 14 | -------------------------------------------------------------------------------- /JavaScript/helpers/do-notation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (generator) => { 4 | const sequence = generator(); 5 | 6 | const step = (value) => { 7 | const result = sequence.next(value); 8 | if (result.done) return result.value; 9 | return result.value.then(step); 10 | }; 11 | 12 | return step(); 13 | }; 14 | -------------------------------------------------------------------------------- /JavaScript/h-yield-gen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* gen1() { 4 | yield 10; 5 | yield 20; 6 | yield 30; 7 | } 8 | 9 | function* gen2() { 10 | yield 40; 11 | yield 50; 12 | yield 60; 13 | } 14 | 15 | function* genFn() { 16 | yield* gen1(); 17 | yield* gen2(); 18 | } 19 | 20 | console.log('[...genFn()] =', [...genFn()]); 21 | -------------------------------------------------------------------------------- /JavaScript/d-for-of.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* ids(...args) { 4 | let i = 0; 5 | while (args.length > i) { 6 | const id = args[i++]; 7 | if (id === undefined) return; 8 | yield id; 9 | } 10 | } 11 | 12 | const id = ids(1011, 1078, 1292, 1731, undefined, 1501, 1550); 13 | for (const val of id) { 14 | console.log({ val }); 15 | } 16 | -------------------------------------------------------------------------------- /JavaScript/c-return.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* ids(...args) { 4 | let i = 0; 5 | while (args.length > i) { 6 | const id = args[i++]; 7 | if (id === undefined) return; 8 | yield id; 9 | } 10 | } 11 | 12 | const id = ids(1011, 1078, 1292, 1731, undefined, 1501, 1550); 13 | let val; 14 | do { 15 | val = id.next(); 16 | console.log({ val }); 17 | } while (!val.done); 18 | -------------------------------------------------------------------------------- /JavaScript/helpers/maybe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Just(value) { 4 | return { 5 | toString() { 6 | return `Just ${value}`; 7 | }, 8 | 9 | then(fn) { 10 | return fn(value); 11 | }, 12 | }; 13 | } 14 | 15 | const Nothing = { 16 | toString() { 17 | return 'Nothing'; 18 | }, 19 | 20 | then() { 21 | return Nothing; 22 | }, 23 | }; 24 | 25 | module.exports = { Just, Nothing }; 26 | -------------------------------------------------------------------------------- /JavaScript/b-yield.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* counter(begin, end, delta = 1) { 4 | let value = begin; 5 | while (end > value) { 6 | value += delta; 7 | if (value > end) return; 8 | yield value; 9 | } 10 | } 11 | 12 | const c = counter(0, 30, 12); 13 | const val1 = c.next(); 14 | const val2 = c.next(); 15 | const val3 = c.next(); 16 | const val4 = c.next(); 17 | console.log({ c, val1, val2, val3, val4 }); 18 | -------------------------------------------------------------------------------- /JavaScript/6-do-maybe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Just } = require('./helpers/maybe.js'); 4 | 5 | const doMonad = require('./helpers/do-notation.js'); 6 | 7 | Just(5).then((x) => 8 | Just(x + 3) 9 | .then((x) => 10 | Just(x * 4), 11 | ).then(console.log)); 12 | 13 | doMonad(function* () { 14 | const a = yield Just(5); 15 | const b = yield Just(3); 16 | const c = yield Just(4); 17 | console.log((a + b) * c); 18 | }); 19 | -------------------------------------------------------------------------------- /JavaScript/f-next.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* counter(begin, end, delta) { 4 | let value = begin; 5 | while (end > value) { 6 | value += delta; 7 | const back = yield value; 8 | if (back) value += back; 9 | console.log({ back }); 10 | } 11 | } 12 | 13 | const c = counter(0, 30, 12); 14 | const val1 = c.next(); 15 | const val2 = c.next(); 16 | const val3 = c.next(150); 17 | const val4 = c.next(); 18 | console.log({ c, val1, val2, val3, val4 }); 19 | -------------------------------------------------------------------------------- /JavaScript/7-do-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Run `helpers/server.js` for this example to work. 4 | 5 | const getJSON = require('./helpers/get-json.js'); 6 | const doMonad = require('./helpers/do-notation.js'); 7 | 8 | const baseUrl = 'http://localhost:3000/'; 9 | doMonad(function* () { 10 | const api = yield getJSON(baseUrl); 11 | for (const resource of api.resources) { 12 | const data = yield getJSON(baseUrl + resource); 13 | console.log(data); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /JavaScript/8-catch-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Run `helpers/server.js` for this example to work. 4 | 5 | const getJSON = require('./helpers/get-json.js'); 6 | const doMonad = require('./helpers/do-notation.js'); 7 | 8 | const baseUrl = 'http://localhost:3000/'; 9 | doMonad(function* () { 10 | const api = yield getJSON(baseUrl); 11 | for (const resource of api.resources) { 12 | const data = yield getJSON(baseUrl + resource); 13 | console.log(data); 14 | } 15 | }).catch((err) => { 16 | console.log(err); 17 | }); 18 | -------------------------------------------------------------------------------- /Exercises/1-ids.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ids = function* () { 4 | const free = ['0']; 5 | const prepared = { has: false, value: '' }; 6 | 7 | while (true) { 8 | if (prepared.has) { 9 | prepared.has = false; 10 | yield prepared.value; 11 | } 12 | const nextFree = free.shift(); 13 | free.push('01' + nextFree); 14 | free.push('00' + nextFree); 15 | prepared.value = '11' + nextFree; 16 | prepared.has = true; 17 | yield '10' + nextFree; 18 | } 19 | }; 20 | 21 | module.exports = { ids }; 22 | -------------------------------------------------------------------------------- /JavaScript/i-return.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* genFn() { 4 | yield 10; 5 | yield 20; 6 | yield 30; 7 | } 8 | 9 | { 10 | const g = genFn(); 11 | const val1 = g.next(); 12 | const val2 = g.next(); 13 | const val3 = g.next(); 14 | const val4 = g.return(40); 15 | console.log({ val1, val2, val3, val4 }); 16 | } 17 | 18 | { 19 | const g = genFn(); 20 | const val1 = g.next(); 21 | const val2 = g.return(40); 22 | const val3 = g.next(); 23 | const val4 = g.return(50); 24 | console.log({ val1, val2, val3, val4 }); 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Check labs 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Node.js 11 | uses: actions/setup-node@v1 12 | with: 13 | node-version: 14 14 | - uses: actions/cache@v2 15 | with: 16 | path: ~/.npm 17 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 18 | restore-keys: | 19 | ${{ runner.os }}-node- 20 | - run: npm ci 21 | - run: npm t 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "version": "1.0.4", 5 | "author": "Timur Shemsedinov ", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "eslint ./Exercises; hpw", 9 | "ci": "eslint ./Exercises && hpw", 10 | "lint": "eslint . && prettier -c \"**/*.js\"", 11 | "fix": "eslint . --fix && prettier --write \"**/*.js\"" 12 | }, 13 | "dependencies": { 14 | "eslint": "^7.5.0", 15 | "hpw": "^0.1.14", 16 | "eslint": "^9.12.0", 17 | "eslint-config-metarhia": "^9.1.1", 18 | "prettier": "^3.3.3" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Python/2-generator.py: -------------------------------------------------------------------------------- 1 | def Range(first, second=None, step=1): 2 | if second is None: 3 | current = 0 4 | end = first 5 | else: 6 | current = first 7 | end = second 8 | while not (current >= end and step > 0 9 | or current <= end and step < 0): 10 | yield current 11 | current += step 12 | 13 | def print_space(str): 14 | print("{}".format(str), end=' ') 15 | 16 | 17 | for i in Range(10): 18 | print_space(i) 19 | print() 20 | for i in Range(3, 18): 21 | print_space(i) 22 | print() 23 | for i in Range(2, 15, 2): 24 | print_space(i) 25 | print() 26 | for i in Range(10, 0, -1): 27 | print_space(i) 28 | -------------------------------------------------------------------------------- /Python/3-primes.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | def primes(n): 4 | number = 0 5 | count = 0 6 | while count < n: 7 | while True: 8 | if is_prime(number): 9 | yield number 10 | count += 1 11 | number += 1 12 | break 13 | number += 1 14 | 15 | def is_prime(number): 16 | if number > 1: 17 | if number == 2: 18 | return True 19 | if number % 2 == 0: 20 | return False 21 | for current in range(3, int(math.sqrt(number) + 1), 2): 22 | if number % current == 0: 23 | return False 24 | return True 25 | return False 26 | 27 | for i in primes(10): 28 | print(i) 29 | -------------------------------------------------------------------------------- /JavaScript/2-range-generator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* range(first, second, step = 1) { 4 | let current, end; 5 | 6 | if (second === undefined) { 7 | current = 0; 8 | end = first; 9 | } else { 10 | current = first; 11 | end = second; 12 | } 13 | 14 | if (step > 0) { 15 | while (current < end) { 16 | yield current; 17 | current += step; 18 | } 19 | } else { 20 | while (current > end) { 21 | yield current; 22 | current += step; 23 | } 24 | } 25 | } 26 | 27 | console.log([ 28 | [...range(10)], 29 | [...range(3, 18)], 30 | [...range(2, 15, 2)], 31 | [...range(10, 0, -1)], 32 | ]); 33 | -------------------------------------------------------------------------------- /JavaScript/3-primes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* numbers(start) { 4 | while (true) { 5 | yield start++; 6 | } 7 | } 8 | 9 | function* sieve(sequence, prime) { 10 | for (const number of sequence) { 11 | if (number % prime !== 0) { 12 | yield number; 13 | } 14 | } 15 | } 16 | 17 | function* primes() { 18 | let sequence = numbers(2); 19 | let prime; 20 | while (true) { 21 | prime = sequence.next().value; 22 | yield prime; 23 | sequence = sieve(sequence, prime); 24 | } 25 | } 26 | 27 | function* take(count, sequence) { 28 | for (let i = 0; i < count; i++) { 29 | yield sequence.next().value; 30 | } 31 | } 32 | 33 | for (const prime of take(10, primes())) { 34 | console.log(prime); 35 | } 36 | -------------------------------------------------------------------------------- /JavaScript/helpers/get-json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('node:http'); 4 | 5 | module.exports = (url) => new Promise((resolve, reject) => { 6 | http.get(url, (res) => { 7 | const code = res.statusCode; 8 | if (code !== 200) { 9 | reject(new Error(`HTTP status code ${code}`)); 10 | return; 11 | } 12 | 13 | res.on('error', reject); 14 | 15 | const chunks = []; 16 | res.on('data', (chunk) => { 17 | chunks.push(chunk); 18 | }); 19 | 20 | res.on('end', () => { 21 | const json = Buffer.concat(chunks).toString(); 22 | try { 23 | const object = JSON.parse(json); 24 | resolve(object); 25 | } catch (error) { 26 | reject(error); 27 | } 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /JavaScript/9-maybe-catch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const doMonad = require('./helpers/do-notation.js'); 4 | 5 | const Nothing = { 6 | then() { 7 | return Nothing; 8 | }, 9 | 10 | catch(callback) { 11 | callback(); 12 | return this; 13 | }, 14 | }; 15 | 16 | class Just { 17 | constructor(value) { 18 | this.value = value; 19 | } 20 | 21 | then(fn) { 22 | const result = fn(this.value); 23 | if (result instanceof Just || result === Nothing) { 24 | return result; 25 | } 26 | return new Just(result); 27 | } 28 | 29 | catch() { 30 | return this; 31 | } 32 | } 33 | 34 | doMonad(function* () { 35 | const a = yield new Just(5); 36 | const b = yield new Just(3); 37 | const c = yield new Just(4); 38 | console.log((a + b) * c); 39 | }).catch(() => { 40 | console.log('At least one value is not present'); 41 | }); 42 | -------------------------------------------------------------------------------- /JavaScript/5-producer-consumer-send.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* sleep(interval) { 4 | const start = new Date(); 5 | while (new Date() - start < interval) { 6 | yield; 7 | } 8 | } 9 | 10 | function* produce(consumer) { 11 | while (true) { 12 | yield* sleep(500); 13 | consumer.next(Math.random()); 14 | } 15 | } 16 | 17 | function* consume() { 18 | let count = 0; 19 | let sum = 0; 20 | while (true) { 21 | const data = yield; 22 | count++; 23 | sum += data; 24 | console.log( 25 | `Got data: ${data}\n` + 26 | `Count: ${count}\n` + 27 | `Sum: ${sum}\n` + 28 | `Average: ${sum / count}\n`, 29 | ); 30 | } 31 | } 32 | 33 | const consumer = consume(); 34 | const producer = produce(consumer); 35 | consumer.next(); 36 | 37 | const step = () => { 38 | producer.next(); 39 | setImmediate(step); 40 | }; 41 | 42 | step(); 43 | -------------------------------------------------------------------------------- /Python/1-range.py: -------------------------------------------------------------------------------- 1 | class Range: 2 | 3 | def __init__(self, first, second=None, step=1): 4 | if second is None: 5 | self.start = 0 6 | self.current = 0 7 | self.end = first 8 | self.step = step 9 | else: 10 | self.start = first 11 | self.current = first 12 | self.end = second 13 | self.step = step 14 | 15 | def __iter__(self): 16 | return self 17 | 18 | def __next__(self): 19 | result = self.current 20 | if (result >= self.end and self.step > 0 21 | or result <= self.end and self.step < 0): 22 | raise StopIteration 23 | else: 24 | self.current += self.step 25 | return result 26 | 27 | def print_space(str): 28 | print("{}".format(str), end=' ') 29 | 30 | for i in Range(10): 31 | print_space(i) 32 | print() 33 | for i in Range(3, 18): 34 | print_space(i) 35 | print() 36 | for i in Range(2, 15, 2): 37 | print_space(i) 38 | print() 39 | for i in Range(10, 0, -1): 40 | print_space(i) 41 | -------------------------------------------------------------------------------- /JavaScript/j-throw.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* genFn() { 4 | try { 5 | yield 10; 6 | } catch (err) { 7 | console.error('intercepted', err); 8 | } 9 | try { 10 | yield 20; 11 | } catch (err) { 12 | console.error('intercepted', err); 13 | } 14 | try { 15 | yield 30; 16 | } catch (err) { 17 | console.error('intercepted', err); 18 | } 19 | } 20 | 21 | try { 22 | const g = genFn(); 23 | const val1 = g.next(); 24 | const val2 = g.next(); 25 | const val3 = g.next(); 26 | const val4 = g.throw('Error message'); 27 | console.log({ val1, val2, val3, val4 }); 28 | } catch (err) { 29 | console.error(err); 30 | } 31 | 32 | try { 33 | const g = genFn(); 34 | const val1 = g.next(); 35 | const val2 = g.throw('Error message 1'); 36 | const val3 = g.next(); 37 | const val4 = g.throw('Error message 2'); 38 | console.log({ val1, val2, val3, val4 }); 39 | } catch (err) { 40 | console.error(err); 41 | } 42 | -------------------------------------------------------------------------------- /JavaScript/helpers/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('node:http'); 4 | 5 | const routes = { 6 | '/': (request, callback) => { 7 | callback({ 8 | apiVersion: '1.0', 9 | resources: ['person', 'city'], 10 | }); 11 | }, 12 | 13 | '/person': (request, callback) => { 14 | callback({ 15 | name: 'アレクセイ', 16 | age: 20, 17 | }); 18 | }, 19 | 20 | '/city': (request, callback) => { 21 | callback({ 22 | name: 'Kyiv', 23 | country: 'Ukraine', 24 | }); 25 | }, 26 | }; 27 | 28 | const server = http.createServer((req, res) => { 29 | const handler = routes[req.url]; 30 | if (!handler) { 31 | res.writeHead(404); 32 | res.end('Not found'); 33 | return; 34 | } 35 | handler(req, (result) => { 36 | const json = JSON.stringify(result); 37 | res.writeHead(200, { 'Content-Type': 'application/json' }); 38 | res.end(json); 39 | }); 40 | }); 41 | 42 | server.listen(3000); 43 | -------------------------------------------------------------------------------- /JavaScript/4-producer-consumer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function* sleep(interval) { 4 | const start = new Date(); 5 | while (new Date() - start < interval) { 6 | yield; 7 | } 8 | } 9 | 10 | function* produce() { 11 | yield* sleep(500); 12 | return Math.random(); 13 | } 14 | 15 | function* consume() { 16 | let count = 0; 17 | let sum = 0; 18 | while (true) { 19 | const data = yield* produce(); 20 | count++; 21 | sum += data; 22 | console.log( 23 | `Got data: ${data}\n` + 24 | `Count: ${count}\n` + 25 | `Sum: ${sum}\n` + 26 | `Average: ${sum / count}\n`, 27 | ); 28 | } 29 | } 30 | 31 | function* anotherTask() { 32 | while (true) { 33 | yield* sleep(1000); 34 | console.log('Hello!\n'); 35 | } 36 | } 37 | 38 | const consumer = consume(); 39 | const task = anotherTask(); 40 | 41 | const step = () => { 42 | consumer.next(); 43 | task.next(); 44 | setImmediate(step); 45 | }; 46 | 47 | step(); 48 | -------------------------------------------------------------------------------- /JavaScript/1-range.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Range { 4 | constructor(first, second, step = 1) { 5 | if (second === undefined) { 6 | this.start = 0; 7 | this.end = first; 8 | } else { 9 | this.start = first; 10 | this.end = second; 11 | } 12 | this.step = step; 13 | } 14 | 15 | [Symbol.iterator]() { 16 | const { start, end, step } = this; 17 | let current = start; 18 | return { 19 | next() { 20 | if (current === undefined || step === 0) { 21 | return { value: undefined, done: true }; 22 | } 23 | 24 | if ( 25 | current >= end && step > 0 || 26 | current <= end && step < 0 27 | ) { 28 | const value = current; 29 | current = undefined; 30 | return { value, done: true }; 31 | } 32 | 33 | const value = current; 34 | current += step; 35 | return { value, done: false }; 36 | }, 37 | }; 38 | } 39 | } 40 | 41 | console.log([ 42 | [...new Range(10)], 43 | [...new Range(3, 18)], 44 | [...new Range(2, 15, 2)], 45 | [...new Range(10, 0, -1)], 46 | ]); 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2025 How.Programming.Works contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /JavaScript/a-generator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Generator function 4 | 5 | function* genFn(x) { 6 | yield x * 2; 7 | return x * 3; 8 | } 9 | 10 | console.log('genFn =', [genFn]); 11 | console.log('genFn.toString() =', [genFn.toString()]); 12 | console.log('typeof genFn =', typeof genFn); 13 | const fnProto = Object.getPrototypeOf(genFn); 14 | console.log('fnProto.constructor.name =', fnProto.constructor.name); 15 | 16 | console.log('typeof genFn(5) =', typeof genFn(5)); 17 | console.log('genFn(5).toString() =', genFn(5).toString()); 18 | const genProto = Object.getPrototypeOf(genFn(5)); 19 | console.log('genProto =', genProto); 20 | console.log('genProto[Symbol.iterator] =', genProto[Symbol.iterator]); 21 | 22 | console.log('genFn(5) =', genFn(5)); 23 | console.log('genFn(5).next() =', genFn(5).next()); 24 | console.log('genFn(5).next().value =', genFn(5).next().value); 25 | 26 | // Generator class method 27 | 28 | class Multiplier { 29 | constructor(k) { 30 | this.value = k; 31 | } 32 | 33 | * genMethod(a) { 34 | yield this.value; 35 | this.value *= a; 36 | return this.value; 37 | } 38 | } 39 | 40 | const m1 = new Multiplier(2); 41 | console.log('m1.genMethod(5).next() =', m1.genMethod(5).next()); 42 | 43 | // Generator object field 44 | 45 | const m2 = { 46 | value: 2, 47 | 48 | * genMethod(a) { 49 | yield this.value; 50 | this.value *= a; 51 | return this.value; 52 | }, 53 | }; 54 | 55 | console.log('m2.genMethod(5).next() =', m2.genMethod(5).next()); 56 | -------------------------------------------------------------------------------- /Exercises/1-ids.test: -------------------------------------------------------------------------------- 1 | ({ 2 | name: 'ids', 3 | length: [100, 1000], 4 | test: ids => { 5 | { 6 | const array = []; 7 | for (const id of ids()) { 8 | if (id.length > 10) break; 9 | array.push(id); 10 | } 11 | array.sort((a, b) => parseInt(a, 2) - parseInt(b, 2)); 12 | const expected = [ 13 | '100', 14 | '110', 15 | '10000', 16 | '10010', 17 | '11000', 18 | '11010', 19 | '1000000', 20 | '1000010', 21 | '1001000', 22 | '1001010', 23 | '1100000', 24 | '1100010', 25 | '1101000', 26 | '1101010', 27 | '100000000', 28 | '100000010', 29 | '100001000', 30 | '100001010', 31 | '100100000', 32 | '100100010', 33 | '100101000', 34 | '100101010', 35 | '110000000', 36 | '110000010', 37 | '110001000', 38 | '110001010', 39 | '110100000', 40 | '110100010', 41 | '110101000', 42 | '110101010', 43 | ]; 44 | al = array.length; 45 | el = expected.length; 46 | if (al !== el) { 47 | const msg = `Result length is ${al} instead of expected ${el}`; 48 | throw new Error(msg); 49 | } 50 | for (let i = 0; i < al; i++) { 51 | const x = array[i]; 52 | const y = expected[i]; 53 | if (array[i] !== expected[i]) { 54 | const msg = `Element result[${i}] === ${x} instead of expected ${y}`; 55 | throw new Error(msg); 56 | } 57 | } 58 | } 59 | } 60 | }) 61 | -------------------------------------------------------------------------------- /JavaScript/theory/presentation.md: -------------------------------------------------------------------------------- 1 | Генераторы в JavaScript. Часть 1 2 | ================================ 3 | 4 | @aqrln 5 | 6 | --- 7 | 8 | ### Итерабельные объекты и итераторы в ES6 9 | 10 | Среди нововведений в ES6, помимо новых синтаксических конструкций и типов 11 | данных, есть и два новых интерфейса: протокол итерабельных объектов и протокол 12 | итераторов. 13 | 14 | MDN: 15 | 16 | * [Iterable protocol](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols#iterable) 17 | * [Iterator protocol](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols#iterator) 18 | 19 | --- 20 | 21 | ### Iterable Protocol 22 | 23 | ```javascript 24 | let myIterable = { 25 | ... 26 | 27 | [Symbol.iterator]() { 28 | let iterator = ...; 29 | return iterator; 30 | } 31 | 32 | ... 33 | } 34 | ``` 35 | 36 | --- 37 | 38 | ### Iterator Protocol 39 | 40 | ```javascript 41 | let iterator = { 42 | ... 43 | 44 | next() { 45 | // Если ещё остались значения 46 | return { 47 | value: currentValue, 48 | done: false 49 | }; 50 | 51 | // Если значения закончились 52 | return { 53 | value: undefined, 54 | done: true 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | --- 61 | 62 | ### Примеры итерабельных объектов 63 | 64 | * Array 65 | * TypedArray 66 | * String 67 | * Buffer 68 | * Map 69 | * Set 70 | 71 | --- 72 | 73 | ### Методы коллекций, которые возвращают итераторы 74 | 75 | * keys() 76 | * values() 77 | * entries() 78 | 79 | --- 80 | 81 | ### Некоторые функции, которые принимают итерабельные объекты 82 | 83 | * Конструкторы новых коллекций в ES6 (Map, Set...) 84 | * Array.from() 85 | * Promise.all(), Promise.race() 86 | 87 | --- 88 | 89 | ### Синтаксические конструкции, которые работают с итерабельными объектами 90 | 91 | * Цикл for-of 92 | * Spread 93 | * Destructuring assignment 94 | * yield* 95 | 96 | --- 97 | 98 | ### Генераторы 99 | 100 | Generator function — это функция, возвращающая специальный итератор (generator 101 | object), управляющий её выполнением. Оператор `yield` в теле функции 102 | приостанавливает выполнение, метод `next()` итератора — продолжает. 103 | 104 | --- 105 | 106 | Объекты-генераторы одновременно являются и итерабельными объектами, и 107 | итераторами: 108 | 109 | ```javascript 110 | > function* generatorFunction() { } 111 | undefined 112 | > let generatorObject = generatorFunction() 113 | undefined 114 | > generatorObject.next 115 | [Function: next] 116 | > generatorObject[Symbol.iterator]() === generatorObject 117 | true 118 | ``` 119 | 120 | --- 121 | 122 | ### Yield 123 | 124 | Оператор `yield` служит для двунаправленного обмена данными с генератором. 125 | Значение, стоящее после ключевого слова `yield`, становится текущим значением 126 | итератора, а в метод `next()` опционально можно передать значение, которое 127 | примет yield-выражение. 128 | 129 | --- 130 | 131 | ### Yield* 132 | 133 | ```javascript 134 | yield* from iterable; 135 | ``` 136 | 137 | #### ≈ 138 | 139 | ```javascript 140 | for (let value of iterable) { 141 | yield value; 142 | } 143 | ``` 144 | 145 | --- 146 | 147 | ### Yield* Expression 148 | 149 | ```javascript 150 | function* g1() { 151 | yield* [1, 2, 3]; 152 | return 'result'; 153 | } 154 | 155 | let result = null; 156 | 157 | function* g2() { 158 | result = yield* g1(); 159 | } 160 | 161 | console.log([...g2()]); // [1, 2, 3] 162 | console.log(result); // 'result' 163 | ``` 164 | 165 | --- 166 | 167 | # Q&A 168 | --------------------------------------------------------------------------------