Skip to content

record helper / span decorators break methods returning objects that are both Promise and AsyncIterable #8

Description

@mcky

Package version

1.2.3

Describe the bug

Bug

This is a niche issue, but I have some classes that wrap Stripe SDK methods which implement auto-agination. They do this by returning an ApiListPromise, a class that extends Promise<ApiList<T>> AND implements AsyncIterable<T>

Wrapping these in record() (or adding a @span or @spanAll decorator) breaks these, as record checks instanceof Promise and calls .then() on the return value

Can understand if this is one not worth fixing as it's so niche, but happy to work on a PR otherwise

In my app code the error looks like so, where StripeService.listSubscriptions has @span

ℹ TypeError:  this.stripe.listSubscriptions(...) is not a function or its return value is not  async iterable
 ⁃ at StripeSyncProvider.hydrateSubscriptions (providers/stripe_sync_provider.ts:284:47)

 ❯ 284 ┃      for await (const stripeSub of this.stripeService.listSubscriptions()) {
 ⁃ at <anonymous> (providers/stripe_sync_provider.ts:252:18)

Failing test-case

test('record preserves AsyncIterable interface on Promise+AsyncIterable return values', async ({ assert }) => {
  const items = [1, 2, 3]
  const asyncIterablePromise = Object.assign(Promise.resolve(items), {
    async *[Symbol.asyncIterator]() {
      for (const item of items) {
        yield item
      }
    },
  })

  const result = record('iterable-op', () => {
    return asyncIterablePromise
  })

  assert.isTrue(
    Symbol.asyncIterator in result,
    'record() should preserve AsyncIterable interface'
  )

  const collected: number[] = []
  for await (const item of result as AsyncIterable<number>) {
    collected.push(item)
  }
  assert.deepEqual(collected, [1, 2, 3])
})

Which results in these 2 failures (the latter if the first is disabled)

ℹ AssertionError: record() should preserve AsyncIterable interface: expected false to be true
 ❯ 158 ┃      assert.isTrue(
   159 ┃        Symbol.asyncIterator in result,
   160 ┃        'record() should preserve AsyncIterable interface'
   161 ┃      )
ℹ TypeError: result is not async iterable
   163 ┃      const collected: number[] = []
 ❯ 164 ┃      for await (const item of result as AsyncIterable<number>) {

Reproduction repo

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions