Skip to content

Commit

Permalink
fix(async): add AsyncStatusesFirstAborted and AsyncStatusesAbortedPen…
Browse files Browse the repository at this point in the history
…ding
  • Loading branch information
artalar committed Apr 28, 2024
1 parent 033799a commit f653bb2
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 38 deletions.
1 change: 1 addition & 0 deletions bench
Submodule bench added at 73455c
45 changes: 40 additions & 5 deletions packages/async/src/withStatusesAtom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createTestCtx } from '@reatom/testing'

import { reatomAsync, withAbort, withCache } from './'
import {
AsyncStatusesAbortedPending,
AsyncStatusesAnotherPending,
AsyncStatusesFirstPending,
AsyncStatusesFulfilled,
Expand All @@ -12,7 +13,8 @@ import {
asyncStatusesInitState,
withStatusesAtom,
} from './withStatusesAtom'
import { sleep } from '@reatom/utils'
import { noop, sleep } from '@reatom/utils'
import { reatomResource } from '../build'

const test = suite('withStatusesAtom')

Expand Down Expand Up @@ -155,10 +157,13 @@ test('withStatusesAtom parallel requests', async () => {
assert.equal(track.lastInput(), neverPending)

const p1 = fetchData(ctx)
const p2 = fetchData(ctx)

assert.equal(track.lastInput(), firstPending)

const p2 = fetchData(ctx)

assert.equal(track.lastInput(), { ...firstPending, isFirstPending: false })

await p1

assert.equal(track.lastInput(), anotherPending)
Expand Down Expand Up @@ -186,7 +191,7 @@ test('reset during pending', async () => {
})

test('do not reject on abort', async () => {
const fetchData = reatomAsync(async () => {}).pipe(
const fetchData = reatomAsync(async () => sleep()).pipe(
withAbort(),
withStatusesAtom(),
)
Expand All @@ -195,8 +200,38 @@ test('do not reject on abort', async () => {
assert.is(ctx.get(fetchData.statusesAtom), asyncStatusesInitState)

fetchData(ctx)
fetchData.abort(ctx)
assert.is(ctx.get(fetchData.statusesAtom).isRejected, false)
fetchData(ctx)
await null
assert.equal(ctx.get(fetchData.statusesAtom), {
isPending: true,
isFulfilled: false,
isRejected: false,
isSettled: false,

isFirstPending: false,
isEverPending: true,
isEverSettled: false,
} satisfies AsyncStatusesAbortedPending)
;`👍` //?
})

test('do not reject on resource abort', async () => {
const fetchData = reatomResource(async (ctx) => {}).pipe(withStatusesAtom())
const ctx = createTestCtx()

ctx.subscribe(fetchData, noop)()
ctx.subscribe(fetchData, noop)
await null
assert.equal(ctx.get(fetchData.statusesAtom), {
isPending: true,
isFulfilled: false,
isRejected: false,
isSettled: false,

isFirstPending: false,
isEverPending: true,
isEverSettled: false,
} satisfies AsyncStatusesAbortedPending)
;`👍` //?
})

Expand Down
81 changes: 48 additions & 33 deletions packages/async/src/withStatusesAtom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { action, Action, atom, Atom } from '@reatom/core'
import { __thenReatomed } from '@reatom/effects'

import { AsyncAction } from '.'
import { isShallowEqual } from '@reatom/utils'
import { isAbort, isShallowEqual } from '@reatom/utils'

export interface AsyncStatusesNeverPending {
isPending: false
Expand All @@ -11,11 +11,8 @@ export interface AsyncStatusesNeverPending {
isSettled: false

isFirstPending: false
// isAnotherPending: false
isEverPending: false
// isNeverPending: true
isEverSettled: false
// isNeverSettled: true
}

export interface AsyncStatusesFirstPending {
Expand All @@ -25,11 +22,30 @@ export interface AsyncStatusesFirstPending {
isSettled: false

isFirstPending: true
// isAnotherPending: false
isEverPending: true
// isNeverPending: false
isEverSettled: false
// isNeverSettled: true
}

export interface AsyncStatusesFirstAborted {
isPending: false
isFulfilled: false
isRejected: false
isSettled: false

isFirstPending: false
isEverPending: true
isEverSettled: false
}

export interface AsyncStatusesAbortedPending {
isPending: true
isFulfilled: false
isRejected: false
isSettled: false

isFirstPending: false
isEverPending: true
isEverSettled: false
}

export interface AsyncStatusesFulfilled {
Expand All @@ -39,11 +55,8 @@ export interface AsyncStatusesFulfilled {
isSettled: true

isFirstPending: false
// isAnotherPending: false
isEverPending: true
// isNeverPending: false
isEverSettled: true
// isNeverSettled: false
}

export interface AsyncStatusesRejected {
Expand All @@ -53,11 +66,8 @@ export interface AsyncStatusesRejected {
isSettled: true

isFirstPending: false
// isAnotherPending: false
isEverPending: true
// isNeverPending: false
isEverSettled: true
// isNeverSettled: false
}

export interface AsyncStatusesAnotherPending {
Expand All @@ -67,19 +77,18 @@ export interface AsyncStatusesAnotherPending {
isSettled: false

isFirstPending: false
// isAnotherPending: true
isEverPending: true
// isNeverPending: false
isEverSettled: true
// isNeverSettled: false
}

export type AsyncStatusesPending =
| AsyncStatusesFirstPending
| AsyncStatusesAbortedPending
| AsyncStatusesAnotherPending

export type AsyncStatuses =
| AsyncStatusesNeverPending
| AsyncStatusesFirstAborted
| AsyncStatusesPending
| AsyncStatusesFulfilled
| AsyncStatusesRejected
Expand All @@ -102,11 +111,8 @@ export const asyncStatusesInitState: AsyncStatuses = {
isSettled: false,

isFirstPending: false,
// isAnotherPending: false,
isEverPending: false,
// isNeverPending: true,
isEverSettled: false,
// isNeverSettled: true,
}

export const withStatusesAtom =
Expand All @@ -117,6 +123,8 @@ export const withStatusesAtom =
>() =>
(anAsync: T): T & { statusesAtom: AsyncStatusesAtom } => {
if (!anAsync.statusesAtom) {
// `anAsync.onCall` is global over all contexts,
// so we should compute each `statusesAtom` only from related promises
const relatedPromisesAtom = atom(
new WeakSet<Promise<any>>(),
`${anAsync.__reatom.name}.statusesAtom._relatedPromisesAtom`,
Expand All @@ -131,20 +139,18 @@ export const withStatusesAtom =
statusesAtom.__reatom.computer = (ctx, state: AsyncStatuses) => {
ctx.spy(anAsync, ({ payload }) => {
ctx.get(relatedPromisesAtom).add(payload)
const pending = ctx.get(anAsync.pendingAtom)
state = memo(
(statuses) =>
({
isPending: ctx.get(anAsync.pendingAtom) > 0,
isPending: pending > 0,
isFulfilled: false,
isRejected: false,
isSettled: false,

isFirstPending: !statuses.isEverSettled,
// isAnotherPending: statuses.isEverPending,
isFirstPending: !statuses.isEverPending,
isEverPending: true,
// isNeverPending: false,
isEverSettled: statuses.isEverSettled,
// isNeverSettled: statuses.isNeverSettled,
}) as AsyncStatuses,
)(state)
})
Expand Down Expand Up @@ -180,34 +186,43 @@ export const withStatusesAtom =
isSettled: !isPending,

isFirstPending: false,
// isAnotherPending: false,
isEverPending: true,
// isNeverPending: false,
isEverSettled: true,
// isNeverSettled: false,
} as AsyncStatuses
}),
)
}
},
() => {
(error) => {
if (ctx.get(relatedPromisesAtom).has(payload)) {
const isPending = ctx.get(anAsync.pendingAtom) > 0
statusesAtom(
ctx,
memo(() => {
const isPending = ctx.get(anAsync.pendingAtom) > 0
memo((state) => {
if (isAbort(error) && !state.isEverSettled) {
return {
isPending,
isFulfilled: false,
isRejected: false,
isSettled: false,

isFirstPending: false,
isEverPending: true,
isEverSettled: false,
} satisfies
| AsyncStatusesAbortedPending
| AsyncStatusesFirstAborted
}

return {
isPending,
isFulfilled: false,
isRejected: !isPending,
isSettled: !isPending,

isFirstPending: false,
// isAnotherPending: false,
isEverPending: true,
// isNeverPending: false,
isEverSettled: true,
// isNeverSettled: false,
} as AsyncStatuses
}),
)
Expand Down
1 change: 1 addition & 0 deletions reatom-nextjs
Submodule reatom-nextjs added at 9deda0
1 change: 1 addition & 0 deletions reatom-react-ts
Submodule reatom-react-ts added at f90a16

0 comments on commit f653bb2

Please sign in to comment.