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

no-thanks

v1.0.3

Published

Fine-Grained cancellation primitives

Downloads

4

Readme

No Thanks!

Fine-grained cancellation for promises

The no-thanks package provides a set of utilities to cancel a promise that can be used as a primitives to compose more complex pipelines with the ability to stop execution at any time

npm i --save no-thanks

20 kB source, 5.8 kB minified, 1.8 kB gzipped

:octocat: Docs :link:

Quick links:

In case the website is down all the documentation can be found in the doc directory of the source repository

Motivation

It's a very common situation when a javascript program has to run some expensive or long-running task and then the result of the task or the task itself is no longer needed.

As an example, an application may start a network request (or a group of requests) and then the user clicks "Cancel" button or changes some parameters so we need to abort the request and drop the result.

Standard javascript APIs such as Promise or AsyncFunction has no way to stop a task, neither has a tool to split the task into smaller ones and stop them in a more granular way.

This library provides such tools, along with a cancellation callbacks to free up resources after cancel.

Getting Started

const { CancellablePromise, cancellable, coroutine } = require('no-thanks')

If the package is included in a browser with <script> tag, it can be found at window.noThanks key

Basic Usage

CancellablePromise is a Promise that can be canceled. It extends native Promise for full compatibility

class CancellablePromise (task: Function|Promise, finalizer: ?Function = null) extends Promise

The very basic usage of it:

let job = new CancellablePromise(someTask);

// or alternativelly:
job = cancellable(someTask);

/* ... */

job.cancel()

When the cancel method is called while the someTask is not finished, fulfillment value or the rejection of it will be muted and will not be propagated further.

If someTask has finished execution before the cancel method was called, the cancellation has no effect, but the finalizer (if given) will be called anyway.

Note CacnellablePromise constructor and cancellable function are interchangeable except the fineGrained parameter that will be discussed a bit later

Finalization

CancellablePromise constructor or cancellable function can be called with a callback that will be called after cancel.

This callback is responsible for a finalization of the task, it's often used to do some cleanup when a task was canceled.

The finalizer will be called with the results of promises that are already fulfilled at the time of cancel

// CancellablePromise constructor can also be used here
const job = cancellable(someTask, taskResult => { 
    console.log('finalization')
})
    
/* ... */

job.cancel()

// outputs:
// > finalization

Because finalizer is called asynchronously cancel method of the CacnellablePromise returns a Promise with the result of finalizer call. If the finalizer returns promise it will be automatically chained into a cancellation result promise

const job = cancellable(someTask, () => Promise.resolve(42))

job.cancel()
    .then(answer => console.log(answer))

// prints: 42

Note that the finalizer will be called after cancel() in any case, even if the wrapped promise was successfully fulfilled with a value or rejected

Fine-grained cancellation

To create a cancellable pipeline of promises just call .then and .catch methods of CancellablePromise, then the resulting promise can be canceled in the middle of execution

// CancellablePromise constructor can also be used here
const job = cancellable(task1, (result1, resul2, result3) => {
        console.log('canceled', result1, result2, result3)
    })
    .then(() => task2)
    .then(() => task3)

/* ... wait for task1 to complete and task2 to start */

job.cancel()

// outputs:
// > canceled resul1 result2 undefined

Note that only first two tasks was executed

Async Function

The cancellation of an AsyncFunction is controlled with special argument "fineGrained": boolean in the cancellable signature:

cancellable (task: AsyncFunction|Promise, finalizer: ?Function = null, fineGrained: boolean = true) => CancellablePromise

When it's set to true (default) additional argument will be given to the task function, this argument is used to wrap inner tasks into "grains" to make them cancellable. The argument will be provided at the first position and it's usually named "grain"

const job = cancellable(async (grain, ...args) => {
    await grain(task1)
    /* do other stuff */
    await grain(task2)
    await grain(42) // any value can be wrapped into grain, and passed to the finalizer
    await grain(task3)

    console.log(...agrs)
    /// prints: the answer is 42
})

// run the job
const running = job('the', 'answer', 'is', 42)

/* ... */

running.cancel()

Coroutine

To avoid unnecessary verbosity with a grain argument from the previous example, a generator can be used instead of AsyncFunction. The library provides the coroutine method for that. It takes a generator function as an argument and wraps it into a CancellablePromise.

This approach сould be used to run a set of independent tasks sequentially, but if the next task in pipeline somehow depends on the result of the previous one, then cancellable method should be used.

coroutine (generator: Generator Function, finalizer: ?Function = null, fineGrained: boolean = true) => CancellablePromise

Optionally it takes finalizer as the second argument, and fineGrained as the third, if the fineGrained is set to true (default) then all the Promises that generator yields will be wrapped into grains, like in the previous example:

const job = coroutine(function* (...args) {
    yield task1
    /* do other stuff */
    yield task2
    yield task3

    console.log(...args)
    // prints: the answer is 42
})

const running = job('the', 'answer', 'is', 42)

/* ... */

running.cancel()

Interruptible

All the functions described above cancel a task and run finalizer only after the currently executing task is fulfilled or rejected, but sometimes it's necessary to interrupt a task immediately for example, when a network request was canceled by the user it should be aborted before the response arrives.

For that reason the interruptible method can be used:

interruptible (task: AsyncFunction|Promise, finalizer: ?Function = null, fineGrained: boolean = true) => CancellablePromise

const job = interruptible(async (grain, ...args) => {
    await grain(task1)
    await grain(task2)
    await grain(task3)

    console.log(...agrs)
    /// prints: the answer is 42
})

// run the job
const running = job('the', 'answer', 'is', 42)

/* ... */

running.cancel()

The interface of this method is identical to the cancellable method, except it runs finalize immediatelly after cancel()

Note Use interruptible with care and only if your tasks (grains) support immediate abortion, in other case the state of the current task will be unpredictable.

Check out network request recipe for more real-life example

Compose & Decompose

There are two more utility functions in the public API

compose (promise: Promise|Object) => Promise

and

decompose (promise: Object) => Promise

They are used to avoid automatic chaining when returning Promises from promise resolvers and async functions.


(async () => compose(Promise.resolve(42)))()
    .then(decompose)
    .then(value => value === 42)

More examples and recipes can be found here

Also, take a look at the test directory, it contains a lot of examples of different semantics of the library methods

License

MIT

See LICENSE to see the full text.


All notable changes are documented in the CHANGELOG.md file.

This package can also be found at:

About the Author

I'm a web-developer, based in ~700 km south of the Arctic Circle in the beautiful city of Saint-Petersburg.

Feel free to contact me by email