aico
TypeScript icon, indicating that this package has built-in type declarations

1.0.1 • Public • Published

A.I.C.O 🦄

Abort In COroutines (promise)

npm npm

aico was inspired by redux-saga's Task cancellation. I wanted to use it in promises and found several alternatives. But they are a little bit verbose or lacking. aico writes less and does more. And it supports AbortController and typescript.

aico

(I enjoyed watching A.I.C.O on Netflix)

Example

import { aico } from 'aico'

const promise = aico(function* (signal) {
    try {
        yield fetch('/delay/100', { signal }) // <= This api takes 100ms.
        console.log('1. This is printed.')

        yield fetch('/delay/100', { signal }) // <= This api takes 100ms.
        console.log('2. This is not printed.')
    } finally {
        if (signal.aborted) {
            console.log('3. aborted!')
        }
    }
})

promise.catch(err => {
    console.log(`4. message: ${err.name}`)
    console.log(`5. isAborted: ${err.isAborted}`)
})

setTimeout(() => {
    promise.abort() // <= After 150ms
}, 150)
> output
1. This is printed.
3. aborted!
4. message: AbortError
5. isAborted: true

Install

npm install aico

API

new AbortInCoroutines(generator, options?)

Create an abortable promise using a generator. In a generator, yield is the same as async function's await.

import { AbortInCoroutines } from 'aico'

const promise = new AbortInCoroutines(function* (signal) {
    const result = yield Promise.resolve('hello') // <= result is "hello".

    return result
})

promise.then(val => {
    console.log(val) // => "hello"
})

signal parameter is AbortSignal that can cancel DOM requests such as fetch.

const promise = new AbortInCoroutines(function* (signal) {
    const response = yield fetch('/api/request', { signal })

    console.log('This is not printed.')
})

promise.abort() // <= Abort `/api/request` request.

signal has an aborted property that indicates whether the promise was aborted or not.

const promise = new AbortInCoroutines(function* (signal) {
    try {
        /* ... */
    } finally {
        if (signal.aborted) {
            console.log('aborted!')
        } else {
            console.log('not aborted.')
        }
    }
})

promise.abort() // => "aborted!"

If the yielded promise was created with AbortInCoroutines, the cancellation is propagated.

const subTask = () =>
    new AbortInCoroutines(function* (signal) {
        try {
            /* ... */
        } finally {
            if (signal.aborted) {
                console.log('subTask is aborted!')
            }
        }
    })

const promise = new AbortInCoroutines(function* () {
    yield subTask()
})

promise.abort() // => "subTask is aborted!"

options

signal

This is an option to abort a promise with the signal of the external controller.

const controller = new AbortController()

const promise = new AbortInCoroutines(
    function* (signal) {
        try {
            /* ... */
        } finally {
            if (signal.aborted) {
                console.log('aborted!')
            }
        }
    },
    {
        signal: controller.signal, // 👈 Here, the external controller's signal is used.
    }
)

controller.abort() // => aborted!
unhandledRejection

If there is no catch handler registered when this is true, an unhandledRejection occurs. Default is false.

new AbortInCoroutines(
    function* () {
        /* ... */
    },
    {
        unhandledRejection: true,
    }
).abort()
// => `unhandledRejection` warning is printed.

promise.isAborted

Returns whether the promise is aborted or not.

const promise = new AbortInCoroutines(/* ... */)

console.log(promise.isAborted) // => false

promise.abort()

console.log(promise.isAborted) // => true

promise.abort()

Abort the promise.

aico(generator, options?)

This function can be used instead of the verbose new AbortInCoroutines().

import { aico } from 'aico'

const promise = aico(function* (signal) {
    /* ... */
})

Combinators

all(values)

This is an abortable Promise.all().

import { aico, all } from 'aico'

const fetchData = url =>
    aico(function* (signal) {
        try {
            /* ... */
        } finally {
            if (signal.aborted) {
                console.log(`aborted : ${url}`)
            }
        }
    })

const promise = all([fetchData('/api/1'), fetchData('/api/2'), fetchData('/api/3')])

promise.abort()
// => aborted : /api/1
// => aborted : /api/2
// => aborted : /api/3

If one is rejected, the other promise created by aico is automatically aborted.

const promise = all([fetchData('/api/1'), fetchData('/api/2'), fetchData('/api/3'), Promise.reject('fail')])
// (This is printed immediately)
// => aborted : /api/1
// => aborted : /api/2
// => aborted : /api/3

race(values)

This is an abortable Promise.race().

import { race } from 'aico'

const timeout = ms => new Promise((_, reject) => setTimeout(reject, ms))

const promise = race([
    fetchData('/delay/600'), // <= This api takes 600ms.
    timeout(500),
])

// (After 500ms)
// => aborted : /delay/600

Likewise, if one is rejected, the other promise created by aico is automatically aborted.

any(values)

This is an abortable Promise.any().

allSettled(values)

This is an abortable Promise.allSettled().

cast(promise)

In TypeScript, type inference of yielded promise is difficult. So do type assertions explicitly.

import { aico, cast } from 'aico'

const promise = aico(function* () {
    const data = (yield asyncTask()) as { value: string }

    // or
    const data = (yield asyncTask()) as Awaited<ReturnType<typeof asyncTask>>
})

Or a cast function and yield* combination, type inference is possible without type assertion.

import { aico, cast } from 'aico'

const promise = aico(function* () {
    const data = yield* cast(asyncTask())
})

License

MIT © skt-t1-byungi

Package Sidebar

Install

npm i aico

Weekly Downloads

8

Version

1.0.1

License

MIT

Unpacked Size

27.3 kB

Total Files

6

Last publish

Collaborators

  • skt-t1-byungi