enhance-generator

Enhance GeneratorFunction with mutation and desigion-making methods

Usage no npm install needed!

<script type="module">
  import enhanceGenerator from 'https://cdn.skypack.dev/enhance-generator';
</script>

README

EnhanceGenerator

Supported for ES2018 and above

If you want some middlewares and loop controle methods (sync/async) for GeneratorFunctions(AsyncGeneratorFunctions), then this package is for you.

Usage

Lets say we have GeneratorFunction:

function* genFunc() {
    yield 1
    yield 2
    yield 3
    return 'finish'
}

If you want to map yielded values to another ones:

const { enhance } = require('enhance-generator')

// New GeneratorFunction with additional methods on it.
const enhanced = enhance(genFunc)
  .map((value, index) => value * 100)
  .forEach((value, index) => {
    console.log(`index: ${index}, value: ${value}`)
  })


for(const _ of enhanced());
// will be printed:
// index: 0, value: 100
// index: 1, value: 200
// index: 2, value: 300

// // Or You can manually call **next**.
const gen = enhanced()
while(!gen.next(/* nextArg */).done); // nextArg will be passed to original Generator.

You can break loop, yield value and continue iteration, or skip both iteration rest and yield stage.

  • continue
    const { enhance } = require('enhance-generator')
    
    const values = []
    for (const val of enhance(genFunc)
        .continue((value, index) => index === 1) // skip rest of middlewares when index === 1   
        .forEach((value, index) => {
            console.log(`index: ${index}`)
        })()
    ) {
      values.push(val)
    }
    // will be printed:
    // index: 0
    // index: 2
    
    console.log(values)
    // will be yielded
    // [ 1, 2, 3 ]
    
  • break
    const { enhance } = require('enhance-generator')
    
    for (const _ of enhance(genFunc)
        .break((value, index) => index === 1) // break on second forEach(index===1)
        .forEach((value, index) => {
            console.log(`index: ${index}`)
        })()
    );
    // will be printed:
    // index: 0
    
    // will be yielded
    // [ 1 ]
    
  • skip
    const { enhance } = require('enhance-generator')
    
    for (const _ of enhance(genFunc)
        .skip((value, index) => index === 1) // skip both rest of middlewares and yield stage   
        .forEach((value, index) => {
            console.log(`index: ${index}`)
        })()
    );
    // will be printed:
    // index: 0
    // index: 2
    
    // will be yielded
    // [ 1, 3 ]
    

You can provide context - this both to your GeneratorFunction and to middlewares you use

const { enhance } = require('enhance-generator')

const context = {
    count: 0
}
const enhanced = enhance(genFunc)
    .map((value, index) => value * 100)
    .forEach(function (value, index) {
        this.count++
    })

for (const _ of enhanced
    .useThis(() => context)()
);

console.log(context)
// will be printed:
// { count: 3 }

Only the last useThis of the chain will be called and it will be called at the very beginning of the loop - after GeneratorFunction() call and before first next() call.

All this methods are immutable, i.e. each call of this methods (map, forEach...) will return a new (Async)GeneratorFunction with additional middleware layer.

You can also bind context - this to layer, by providing it as second argument to enhance methods, in this case calls to useThis will not be able to change context of that particular layer.

const { enhance } = require('enhance-generator')

const bindedContext = {
    count: 0
}
const enhanced = enhance(genFunc)
    .map((value, index) => value * 100)
    .forEach(function (value, index) {
        this.count++
    }, bindedContext)

const context = {
    count: 0
}
for (const _ of enhanced
    .useThis(() => context)() // useThis will not be able to change binded contexts
);

console.log(bindedContext)
// will be printed:
// { count: 3 }
console.log(context)
// will be printed:
// { count: 0 }

If Your original GeneratorFunction needs context - this, it will be passed through the enhanced one to it. So the behaviour will be the same for each. e.g.

const { enhance } = require('enhance-generator')

function* genFunc() {
    yield 1 * this
    yield 2 * this
    yield 3 * this
    return 'finish'
}
const context = 100

for (const _ of enhance(genFunc)
    .forEach(function (value, index) {
        console.log(value)
    }).call(context)
);
// will be printed
// 100
// 200
// 300

You can also automate next(arg) call`s argument calculations

const { enhance } = require('enhance-generator')

function* genFunc() {
    let nextArg = yield 'enter loop'
    nextArg = yield nextArg
    nextArg = yield nextArg
    return 'exit loop'
}

let nextArg = 0
for (const _ of enhance(genFunc)
    .useNext(() => ++nextArg)
    .forEach(function (value, index) {
        console.log(value)
    })()
);
// will be printed
// 'enter loop'
// 1
// 2

useNext layers will be called before each iteration except first one, in the same order they occure in the chain, before all other layers. The returned value of the last one will be used to call next(arg) on the Origin Generator.

Example with multiple useNext layers

const { enhance } = require('enhance-generator')

function* genFunc() {
    let nextArg = yield 'enter loop'
    nextArg = yield nextArg
    nextArg = yield nextArg
    return 'exit loop'
}

let nextArg = 0
for (const _ of enhance(genFunc)
    .useNext(() => ++nextArg)
    .useNext(() => ++nextArg)
    .forEach(function (value, index) {
        console.log(value)
    })
    .useNext(() => ++nextArg)()
);
// will be printed
// 'enter loop'
// 3
// 6

NOTE: The first call next(arg) just enter the loop, it`s argument thrown by Generator itself, so the first iteration is skiped.

EnhancedGeneratorFunctions are simple GeneratorFunctions Or AsyncGeneratorFunctions with some additional methods on them, So you can pass them to any library that accepts GeneratorFunctions Or AsyncGeneratorFunctions

Async

You can Use async functions, in layer addition methods (map,forEach...), also you can use AsyncGeneratorFunctions when calling enhance(generatorFunction). Saying async, we mean explicit usage of async keyword. From the point of the time that you used AsyncFunction or AsyncGeneratorFunction, resulting EnhancedGeneratorFunctions will be async as well. You can check if it is with property isAsync on returned EnhancedGeneratorFunctions.

NOTE: In this cases you must be careful and be sure you call them with for await of... and not with for of....

When you instead of async keyword use a simple function which returns Promise it will not be awaited, it will be passed directly (if it expected to be passed, as with map) to next layer as simple value.