npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@datagutt/cancellation-token

v3.1.3

Published

This library provides you ability to cancel async function

Downloads

106

Readme

@kaisukez/cancellation-token

What is this library

This library's idea is based on this proposal tc39/proposal-cancelable-promises which is actually great proposal but it was withdrawn for some reason.

I copied some code from https://github.com/conradreuter/cancellationtoken which is the implementation of the withdrawn proposal.

I also added some useful features that conradreuter/cancellationtoken doesn't have such as

  • new CancellationToken(cancel => {}) Revealing Constructor Pattern (from the withdrawn proposal)
  • await CancellationError.ignoreAsync(() => promise) use to ignore CancellationError in one line without using try-catch
  • AyncCheckpoint.before(token, () => yourFunction()) use to throw CancellationError if the specified token is already cancelled before running function, in one line (you can also use AyncCheckpoint.after and AyncCheckpoint.beforeAfter)
  • await cancel() async cancel function (so that you can wait all async onCancel functions to finish before you move on)

Installation

npm i @kaisukez/cancellation-token

How to import

This package ships both ESM and CJS formats

ES Modules (ESM)

import {
    CancellationToken,
    CancellationError,
    SyncCheckpoint,
    AsyncCheckpoint,
    Task,
} from '@datagutt/cancellation-token'

CommonJS (CJS)

const {
    CancellationToken,
    CancellationError,
    SyncCheckpoint,
    AsyncCheckpoint,
    Task,
} = require('@kaisukez/cancellation-token')

Examples

1. Basic usage

import {
    CancellationToken,
    CancellationError,
    Task,
} from '@kaisukez/cancellation-token'

async function task(token: CancellationToken) {
    let i = 0
    while (true) {
        console.log(`do task i=${i++}`)
        await Task.sleep(500)

        if (token.isCancellationRequested) {
            console.log('do cleanup before throwing CancelError')
            token.throwIfCancellationRequested()
        }
    }
}

async function main() {
    const token = new CancellationToken(cancel => {
        setTimeout(async () => await cancel(), 3000)
    })
    
    // other variations of instantiation
    // const { token, cancel } = CancellationToken.source()
    // const [token, cancel] = CancellationToken.sourceArray()

    try {
        await task(token)
    } catch (error) {
        if (error instanceof CancellationError) {
            console.log('task got canceled')
        } else {
            throw error
        }
    }
}

;(async () => {
    try {
        await main()
    } catch (error) {
        console.error(error)
    }
})()
do task i=0
do task i=1
do task i=2
do task i=3
do task i=4
do task i=5
do cleanup before throwing CancelError
task got canceled

2. Using with AbortController

import fetch from 'node-fetch'
import { AbortController, AbortSignal } from 'node-abort-controller'
import {
    CancellationToken,
    CancellationError,
    Task,
} from '@kaisukez/cancellation-token'

async function fetchData(token: CancellationToken, signal: AbortSignal) {
    const result = await fetch('http://www.google.com', { signal })

    // do something in between
    await Task.sleep(100)
    token.throwIfCancellationRequested()

    const html = await result.text()
    return html.slice(0, 20)
}

async function main() {
    const controller = new AbortController()
    const { token, cancel } = CancellationToken.source()

    const unregister = token.onCancel(() => {
        console.log('onCancel -> controller.abort()')
        controller.abort()
    })
    setTimeout(async () => await cancel(), 400)

    try {
        const html = await fetchData(token, controller.signal)
        console.log('html', html)
    } catch (error: any) {
        if (error instanceof CancellationError) {
            console.log('task got canceled')
        } else if (error?.constructor?.name === 'AbortError') {
            // or you can use (error instanceof AbortError)
            console.log('task got aborted')
        } else {
            throw error
        }
    } finally {
        unregister()
        await cancel()
    }
}

;(async () => {
    try {
        await main()
    } catch (error) {
        console.error(error)
    }
})()

abort case

onCancel -> controller.abort()
task got aborted

cancel case

onCancel -> controller.abort()
task got canceled

success case

html <!doctype html><html

3. Combining token

import {
    CancellationToken,
    CancellationError,
    Task,
} from '@kaisukez/cancellation-token'

async function task(token: CancellationToken, id: number) {
    let i = 0
    while (true) {
        console.log(`id=${id} do task i=${i++}`)
        await Task.sleep(50)

        if (token.isCancellationRequested) {
            console.log(`id=${id} do cleanup before throwing CancelError`)
            token.throwIfCancellationRequested()
        }
    }
}

async function main() {
    const token1 = new CancellationToken(cancel => {
        const timeout = 100
        console.log(`token1 timeout = ${timeout}`)
        setTimeout(async () => {
            console.log('token1 is canceled')
            await cancel()
        }, timeout)
    })
    const token2 = new CancellationToken(cancel => {
        const timeout = 300
        console.log(`token2 timeout = ${timeout}`)
        setTimeout(async () => {
            console.log('token2 is canceled')
            await cancel()
        }, timeout)
    })

    // use CancellationError.ignoreAsync
    // so that you don't have to try-catch CancellationError
    const result = await Promise.all([
        CancellationError.ignoreAsync(() => task(token1, 1)),
        CancellationError.ignoreAsync(() => task(token2, 2)),
        CancellationError.ignoreAsync(() => task(CancellationToken.race([token1, token2]), 3)),
        CancellationError.ignoreAsync(() => task(CancellationToken.all([token1, token2]), 4)),
    ])
    console.log('result', result)
}

;(async () => {
    try {
        await main()
    } catch (error) {
        console.error(error)
    }
})()
token1 timeout = 100
token2 timeout = 300
id=1 do task i=0
id=2 do task i=0
id=3 do task i=0
id=4 do task i=0
id=1 do task i=1
id=2 do task i=1
id=3 do task i=1
id=4 do task i=1
token1 is canceled
id=1 do cleanup before throwing CancelError
id=2 do task i=2
id=3 do cleanup before throwing CancelError
id=4 do task i=2
id=2 do task i=3
id=4 do task i=3
id=2 do task i=4
id=4 do task i=4
id=2 do task i=5
id=4 do task i=5
token2 is canceled
id=2 do cleanup before throwing CancelError
id=4 do cleanup before throwing CancelError
result [ undefined, undefined, undefined, undefined ]

4. Checkpoint

import {
    CancellationToken,
    CancellationError,
    SyncCheckpoint,
    AsyncCheckpoint,
    Task,
} from '@kaisukez/cancellation-token'

async function longRunningTask(id: number) {
    console.log('longRunningTask', id)
    await Task.sleep(500)
}

function longRunningTaskSync(id: number) {
    console.log('longRunningTaskSync', id)
    // do long running task in sync
}

async function task(token: CancellationToken) {
    // #1
    token.throwIfCancellationRequested()
    await longRunningTask(1)

    // #2 - is equivalent to #1
    await AsyncCheckpoint.before(token, () => longRunningTask(2))

    // # 3
    await longRunningTask(3)
    token.throwIfCancellationRequested()

    // #4 - is equivalent to #3
    await AsyncCheckpoint.after(token, () => longRunningTask(4))

    // -------------------------------------------

    // #5
    token.throwIfCancellationRequested()
    longRunningTaskSync(5)
    token.throwIfCancellationRequested()

    // #6 - is equivalent to #5
    SyncCheckpoint.beforeAfter(token, () => longRunningTaskSync(6))

    // -------------------------------------------

    // #7
    await AsyncCheckpoint.before(token, () => Promise.resolve())

    // #8
    SyncCheckpoint.after(token, () => {})

    // #9 - is equivalent to #7 and #8
    // but #9 is preferred
    token.throwIfCancellationRequested()
}

async function main() {
    const token = new CancellationToken(cancel => {
        setTimeout(async () => await cancel(), 1234)
    })

    await CancellationError.ignoreAsync(() => task(token))
}

;(async () => {
    try {
        await main()
    } catch (error) {
        console.error(error)
    }
})()
longRunningTask 1
longRunningTask 2
longRunningTask 3