aico v1.0.1
A.I.C.O 🦄
Abort In COroutines (promise)
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.

(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: trueInstall
npm install aicoAPI
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) // => truepromise.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/3If 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/3race(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/600Likewise, 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