Documentation Index
Fetch the complete documentation index at: https://mintlify.com/reatom/reatom/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The withAbort extension adds abort handling to actions and computed atoms, enabling you to cancel ongoing async operations. It supports multiple strategies for handling concurrent calls:
- last-in-win: Abort previous calls when a new one starts (default)
- first-in-win: Ignore new calls while a previous one is running
- manual: No automatic abort, just adds the
abort action for manual control
- finally: Auto-abort all operations when complete
Type Signature
function withAbort(
strategy?: 'last-in-win' | 'first-in-win' | 'manual' | 'finally'
): <T extends AtomLike>(target: T) => T & AbortExt
interface AbortExt {
abort: Action<[reason?: any]>
}
Parameters
strategy
string
default:"last-in-win"
The abort strategy to use:
'last-in-win' (default): Aborts previous concurrent calls when a new one starts
'first-in-win': Ignores new calls while a previous one is still running
'manual': No automatic abort, just adds the abort action for manual control
'finally': Auto-aborts all operations when the action completes
Return Value
Action to manually abort all active operations. Accepts an optional reason parameter.
Examples
Last-in-Win (Default)
Perfect for search/autocomplete where only the latest request matters:
import { action } from '@reatom/core'
import { withAbort } from '@reatom/core/extensions'
import { wrap } from '@reatom/core/methods'
const fetchUser = action(async (id: number) => {
const response = await wrap(fetch(`/api/user/${id}`))
return response.json()
}).extend(withAbort())
fetchUser(1) // will be aborted
fetchUser(2) // will be aborted
fetchUser(3) // this one wins
First-in-Win
Perfect for preventing duplicate submissions:
const submitForm = action(async (data: FormData) => {
await wrap(fetch('/api/submit', {
method: 'POST',
body: data,
}))
}).extend(withAbort('first-in-win'))
submitForm(data) // runs
submitForm(data) // ignored, returns previous promise
submitForm(data) // ignored, returns previous promise
Manual Abort
Perfect for polling or long-running tasks:
const pollData = action(async () => {
while (true) {
await wrap(sleep(1000))
const data = await wrap(fetch('/api/poll'))
processData(data)
}
}).extend(withAbort('manual'))
// Start polling
pollData()
// Stop polling when needed
pollData.abort()
Manual Abort - Multiple Concurrent Calls
const doSome = action(async (id: string) => {
await wrap(sleep())
return `Result: ${id}`
}).extend(withAbort('manual'))
// All run concurrently
doSome('a')
doSome('b')
doSome('c')
// Abort all at once
doSome.abort()
First-in-Win with Manual Abort
const fetch = action(async (n: number) => {
await wrap(sleep(10))
return n * 2
}).extend(withAbort('first-in-win'))
fetch(1) // runs
fetch(2) // ignored
await wrap(sleep())
fetch.abort() // abort the running operation
await wrap(fetch(3)) // now runs (previous was aborted)
Abort Propagation
Abort signals propagate through nested async operations:
const doSome = action((n: number) => doOther(n), 'doSome').extend(
withAbort()
)
const doOther = action(async (n: number) => {
await wrap(sleep())
return n * 2
}, 'doOther')
const results: number[] = []
doOther.onFulfill.extend(
withCallHook(({ payload }) => results.push(payload))
)
doSome(1)
doSome(2)
doSome(3)
await wrap(sleep())
// Only the last call completed
console.log(results) // → [6]
Abort in Computed Atoms
import { atom, computed } from '@reatom/core'
import { withAbort } from '@reatom/core/extensions'
import { abortVar } from '@reatom/core/methods'
const count = atom(0, 'count')
const logs: string[] = []
computed(async () => {
const state = count()
let running = true
logs.push(`${state} start`)
abortVar.subscribe(() => {
running = false
logs.push(`${state} abort`)
})
while (running) {
logs.push(`${state} loop`)
await wrap(sleep())
}
}, 'loop').subscribe()
await wrap(sleep())
console.log(logs) // → ['0 start', '0 loop', '0 loop', ...]
count.set(1)
await wrap(sleep())
console.log(logs) // → [..., '1 start', '1 loop', ...]
// Previous computation was aborted
Finally Strategy with Race
import { race } from '@reatom/core/methods'
const process = action(async () => {
const usersPromise = abortVar.createAndRun(() =>
fetch('/api/users').then(r => r.json())
)
const postsPromise = abortVar.createAndRun(() =>
fetch('/api/posts').then(r => r.json())
)
// Race to see which completes first
const result = await wrap(race(usersPromise, postsPromise))
// The loser gets aborted automatically
return result
}).extend(withAbort('finally'))
// All unfinished operations abort when process completes
await wrap(process())
Abort with Reason
const longTask = action(async () => {
try {
while (true) {
await wrap(sleep(100))
doWork()
}
} catch (error) {
if (isAbort(error)) {
console.log('Aborted:', error.message)
}
throw error
}
}).extend(withAbort('manual'))
longTask()
// Abort with custom reason
longTask.abort('User cancelled')
// Logs: "Aborted: User cancelled"
Use Cases
Search/Autocomplete
const searchQuery = atom('', 'searchQuery')
const searchResults = computed(async () => {
const query = searchQuery()
if (!query) return []
await wrap(sleep(300)) // debounce
const res = await wrap(fetch(`/api/search?q=${query}`))
return await wrap(res.json())
}, 'searchResults').extend(
withAbort() // last-in-win: only latest search matters
)
const submitOrder = action(async (order: Order) => {
const res = await wrap(fetch('/api/orders', {
method: 'POST',
body: JSON.stringify(order),
}))
return await wrap(res.json())
}, 'submitOrder').extend(
withAbort('first-in-win') // prevent double submission
)
// Double-click protection built-in
submitButton.onclick = () => submitOrder(currentOrder())
Infinite Polling
const startPolling = action(async () => {
while (true) {
try {
await wrap(sleep(5000))
const data = await wrap(fetch('/api/status'))
updateStatus(await wrap(data.json()))
} catch (error) {
if (isAbort(error)) break
console.error('Poll failed:', error)
}
}
}, 'startPolling').extend(withAbort('manual'))
// Start
startPolling()
// Stop when component unmounts
onUnmount(() => startPolling.abort())
Progressive Data Loading
const loadUserData = action(async (userId: string) => {
// Quick data first
const profile = await wrap(fetch(`/api/users/${userId}/profile`))
updateProfile(await wrap(profile.json()))
// Detailed data second (can be aborted if user navigates away)
const details = await wrap(fetch(`/api/users/${userId}/details`))
updateDetails(await wrap(details.json()))
// Heavy data last (can be aborted)
const analytics = await wrap(fetch(`/api/users/${userId}/analytics`))
updateAnalytics(await wrap(analytics.json()))
}, 'loadUserData').extend(withAbort())
// If user changes quickly, old requests are aborted
userIdAtom.subscribe((userId) => {
if (userId) loadUserData(userId)
})
Integration with AbortController
Use abortVar to access the AbortController:
import { abortVar } from '@reatom/core/methods'
const fetchData = action(async (url: string) => {
const controller = abortVar()
const response = await wrap(fetch(url, {
signal: controller?.signal
}))
return await wrap(response.json())
}).extend(withAbort())
- withAsync - Async state tracking (automatically includes withAbort when using withAsyncData)
- withAsyncData - Async data management with abort support
- abortVar - Access AbortController in reactive context