Giter Club home page Giter Club logo

abortable-generator's Introduction

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.