Socket
Socket
Sign inDemoInstall

abortable-generator

Package Overview
Dependencies
26
Maintainers
1
Versions
25
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    abortable-generator

Description


Version published
Maintainers
1
Install size
912 kB
Created

Readme

Source

abortable-generator

Abortable AsyncGenerators using AbortControllers

Installation

npm i --save node-module-template

Usage

Supports both ESM and CommonJS

// esm
import abortable from 'abortable-generator`
// commonjs
const abortable = require('abortable-generator').default

Problem with "stuck" AsyncGenerators

There is a problem with AsyncGenerators where unsettled Promises can prevent them from finishing (and sometimes lead to memory leaks 😱)

async function* getItems() {
  try {
    yield new Promise((resolve) => {})
  } finally {
    // this never happens
  }
}

const items = getItems()

setTimeout(() => {
  // some time later
  items.return()
}, 100)
for await (let item of items) {
  // this never happens
}

Abort "stuck" AsyncGenerators w/ return

Here's an example of how to abort "stuck" AsyncGenerators using abortable-generator

import abortable from 'abortable-generators'

// wrap your AsyncGenerators with `abortable` and use `raceAbort` inside
const getItems = abortable(async function* (raceAbort) {
  yield raceAbort(new Promise((resolve) => {}))
})
const items = getItems()

setTimeout(() => {
  // some time later
  // return will trigger an abort
  items.return()
}, 100)
for await (let item of items) {
  // generator didn't yield anything
}

Abort "stuck" AsyncGenerators w/ return with try...catch...finally

Here's an example of how to abort "stuck" AsyncGenerators using abortable-generator

import abortable from 'abortable-generators'

// wrap your AsyncGenerators with `abortable` and use `raceAbort` inside
const getItems = abortable(async function* (raceAbort) {
  try {
    yield raceAbort(new Promise((resolve) => {}))
  } catch (err) {
    if (err.name === 'AbortError') {
      // return will throw an AbortError, but it will be ignored even if rethrown here
      return
    }
    throw err
  } finally {
    // done
  }
})
const items = getItems()

setTimeout(() => {
  // some time later
  // return will throw an AbortError into the generator
  items.return()
}, 100)
for await (let item of items) {
  // generator didn't yield anything
}

Throw an error into a "stuck" AsyncGenerators w/ throw

Here's an example of how to throw an error into a "stuck" AsyncGenerators using abortable-generator

import abortable from 'abortable-generators'

// wrap your AsyncGenerators with `abortable` and use `raceAbort` inside
const getItems = abortable(async function* (raceAbort) {
  try {
    yield raceAbort(new Promise((resolve) => {}))
  } catch (err) {
    // [Error: boom]
    throw err
  } finally {
    // done
  }
})
const items = getItems()

setTimeout(() => {
  // some time later
  // return will throw this error into the generator
  const err = new Error('boom')
  items.throw(err)
}, 100)
for await (let item of items) {
  // generator didn't yield anything
}

Abort "stuck" AsyncGenerators w/ an AbortSignal

Here's an example of how to abort "stuck" AsyncGenerators using abortable-generator

import abortable from 'abortable-generators'

const controller = new AbortController()
const signal = controller.signal

// wrap your AsyncGenerators with `abortable` and use `raceAbort` inside
const getItems = abortable(async function* (raceAbort) {
  try {
    yield raceAbort(new Promise((resolve) => {}))
  } catch (err) {
    if (err.name === 'AbortError') {
      // abort will throw an AbortError, but it will be ignored even if rethrown here
      return
    }
    throw err
  } finally {
    // done
  }
})

// abort-signal can also be used to abort abortable generators
const items = getItems(signal)

setTimeout(() => {
  // some time later
  controller.abort()
}, 100)
for await (let item of items) {
  //
}

Abort AsyncGenerators containing cancellable Promises

Here's an example of how to abort AsyncGenerators that contain cancellable Promises (supporting AbortSignal)

import abortable from 'abortable-generators'

const getItems = abortable(async function* (raceAbort) {
  try {
    yield raceAbort((signal) => fetch('https://codeshare.io', { signal }))
  } catch (err) {
    if (err.name === 'AbortError') {
      // cancelled fetch will throw an AbortError, but it will be ignored even if rethrown here
      return
    }
    throw err
  } finally {
    // done
  }
})

const items = getItems()

setTimeout(() => {
  // some time later
  items.return()
}, 100)
for await (let item of items) {
  //
}

Advanced Example: Abort nested AsyncGenerators w/ multiple Promises and external AbortSignal

Here's an example of how to abort AsyncGenerators that contain cancellable Promises (supporting AbortSignal)

import abortable from 'abortable-generators'

const controller = new AbortController()
const signal = controller.signal

const getMoreItems = abortable(async function* (raceAbort) {
  try {
    await raceAbort(timeoutPromise(100))
    yield raceAbort((signal) => fetch('https://codeshare.io', { signal }))
  } catch (err) {
    if (err.name === 'AbortError') {
      // aborted promises will throw an AbortError, but it will be ignored even if rethrown here
      return
    }
    throw err
  } finally {
    // done
  }
})
const getItems = abortable(async function* (raceAbort) {
  let moreItems
  try {
    moreItems = await raceAbort(signal => getMoreItems(signal))
    for await (let item in moreItems) {
      yield item
    }
    await raceAbort(timeoutPromise(100))
    yield raceAbort((signal) => fetch('https://codeshare.io', { signal }))
  } catch (err) {
    if (err.name === 'AbortError') {
      // aborted promises will throw an AbortError, but it will be ignored even if rethrown here
      return
    }
    // if you want to throw an error into a child generator you'll need to call throw manually
    // if (err instanceof FooError) await moreItems.throw(err)
    throw err
  } finally {
    // done
    await moreItems?.return()
  }
})
// this signal can also be used to abort the generator!
const items = getItems(signal)

setTimeout(() => {
  // some time later
  items.return()
}, 100)
for await (let item of items) {
  //
}

License

MIT

FAQs

Last updated on 01 Jul 2022

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc