Skip to content

Not that great for implementing transducers? #48

@jedwards1211

Description

@jedwards1211

Since the API is so similar to Observable, I find it great for consuming observable-like things but I'm not as sold for using it on non-observable-like things, like transducers or other async iterator combiners.

It seems almost simpler to implement transducers like map correctly using a raw async iterator protocol than using Repeater.

The naive assumption (in repeaterMap below) is to use a for await loop, but this keeps consuming input after the caller returns, which would be especially bad if the input is infinite. Whereas a raw async iterator can immediately forward return and throw calls to the input.

A less naive attempt (awkwardRepeaterMap) is to do the for await in a separate function and break once stop is detected. However more calls to next() still go through than with the raw async iterator.

Thoughts?

Code

const { Repeater } = require('@repeaterjs/repeater')

const range = n => ({
  i: 0,
  n,
  async next() {
    console.log('next()')
    const i = ++this.i
    if (i >= this.n) return { done: true }
    return { value: i, done: false }
  },
  async return() {
    console.log('return()')
    this.i = this.n
    return { done: true }
  },
  async throw(error) {
    console.log('throw()')
    this.i = this.n
    return { done: true }
  },
  [Symbol.asyncIterator]() {
    return this
  },
})

const repeaterMap = (iterable, iteratee) =>
  new Repeater(async (push, stop) => {
    for await (const value of iterable) push(iteratee(value))
    await stop
  })

const rawMap = (iterable, iteratee) => ({
  iterator: iterable[Symbol.asyncIterator](),
  async next() {
    const { value, done } = await this.iterator.next()
    return { value: iteratee(value), done }
  },
  return() {
    return this.iterator.return()
  },
  throw(error) {
    return this.iterator.throw(error)
  },
  [Symbol.asyncIterator]() {
    return this
  },
})


const awkwardRepeaterMap = (iterable, iteratee) =>
  new Repeater(async (push, stop) => {
    let stopped = false
    async function next() {
      for await (const value of iterable) {
        if (stopped) break
        push(iteratee(value))
      }
    }
    next()
    await stop
    stopped = true
  })

async function go() {
  console.log('rawMap')
  for await (const i of rawMap(range(10), i => i * 2)) {
    console.log(i)
    if (i >= 5) break
  }
  console.log('\nrepeaterMap')
  for await (const i of repeaterMap(range(10), i => i * 2)) {
    console.log(i)
    if (i >= 5) break
  }
  console.log('\nawkwardRepeaterMap')
  for await (const i of awkwardRepeaterMap(range(10), i => i * 2)) {
    console.log(i)
    if (i >= 5) break
  }
}

go()

Output

rawMap
next()
2
next()
4
next()
6
return()

repeaterMap
next()
next()
next()
2
next()
next()
next()
4
next()
next()
6
next()
next()

awkwardRepeaterMap
next()
next()
next()
2
next()
next()
next()
4
next()
next()
6
next()
return()

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements to docs are requestedquestionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions