fix recursivity, some edge cases

This commit is contained in:
Javier Gonzalez
2019-10-08 19:13:31 +02:00
parent af6b1914ea
commit 475bb2e98f
2 changed files with 97 additions and 21 deletions

View File

@@ -4,7 +4,8 @@ import { decorateMethodOrField } from "./decorator-utils"
import { fail } from "./utils"
let runId = 0
const runningIds = new Set<number>()
const unfinishedIds = new Set<number>()
const currentlyActiveIds = new Set<number>()
interface IActionAsyncContext {
runId: number
@@ -38,12 +39,13 @@ export async function task<R>(promise: Promise<R>): Promise<R> {
const nextStep = step + 1
actionAsyncContextStack.pop()
_endAction(actionRunInfo)
currentlyActiveIds.delete(runId)
try {
return await promise
} finally {
// only restart if it not a dangling promise (the action is not yet finished)
if (runningIds.has(runId)) {
if (unfinishedIds.has(runId)) {
const actionRunInfo = _startAction(
getActionAsyncName(actionName, runId, nextStep),
this,
@@ -58,6 +60,7 @@ export async function task<R>(promise: Promise<R>): Promise<R> {
args,
scope
})
currentlyActiveIds.add(runId)
}
}
}
@@ -169,7 +172,7 @@ function actionAsyncFn(actionName: string, fn: Function): Function {
return async function(this: any, ...args: any) {
const nextRunId = runId++
runningIds.add(nextRunId)
unfinishedIds.add(nextRunId)
const actionRunInfo = _startAction(getActionAsyncName(actionName, nextRunId, 0), this, args)
@@ -181,6 +184,7 @@ function actionAsyncFn(actionName: string, fn: Function): Function {
args,
scope: this
})
currentlyActiveIds.add(nextRunId)
let errThrown: any
try {
@@ -190,17 +194,19 @@ function actionAsyncFn(actionName: string, fn: Function): Function {
errThrown = err
throw err
} finally {
runningIds.delete(nextRunId)
unfinishedIds.delete(nextRunId)
const ctx = actionAsyncContextStack.pop()
if (!ctx || ctx.runId !== nextRunId) {
fail(
"'actionAsync' context not present or invalid. did you await inside an 'actionAsync' without using 'task(promise)'?"
)
if (currentlyActiveIds.has(nextRunId)) {
const ctx = actionAsyncContextStack.pop()
if (!ctx || ctx.runId !== nextRunId) {
fail(
"'actionAsync' context not present or invalid. did you await inside an 'actionAsync' without using 'task(promise)'?"
)
}
ctx.actionRunInfo.error = errThrown
_endAction(ctx.actionRunInfo)
currentlyActiveIds.delete(nextRunId)
}
ctx.actionRunInfo.error = errThrown
_endAction(ctx.actionRunInfo)
}
}
}

View File

@@ -17,6 +17,15 @@ function delayThrow<T>(time: number, value: T) {
})
}
function delayFn(time: number, fn: () => void) {
return new Promise(resolve => {
setTimeout(() => {
fn()
resolve()
}, time)
})
}
function expectNoActionsRunning() {
const obs = mobx.observable.box(1)
const d = mobx.reaction(() => obs.get(), () => {})
@@ -326,7 +335,7 @@ test("dangling promises created indirectly inside the action should be ok", asyn
expectNoActionsRunning()
})
test("dangling promises created directly inside the action using task should throw", async () => {
test("dangling promises created directly inside the action using task should be ok", async () => {
mobx.configure({ enforceActions: "observed" })
let danglingP
@@ -334,14 +343,7 @@ test("dangling promises created directly inside the action using task should thr
danglingP = task(delay(100, 1)) // dangling promise
})
try {
await f1()
fail("should fail")
} catch (e) {
expect(e.message).toBe(
"[mobx-utils] 'actionAsync' context not present or invalid. did you await inside an 'actionAsync' without using 'task(promise)'?"
)
}
await f1()
expect(danglingP).toBeTruthy()
await danglingP
@@ -362,3 +364,71 @@ test("dangling promises created directly inside the action without using task be
await danglingP
expectNoActionsRunning()
})
test("it should support recursive async (with task)", async () => {
mobx.configure({ enforceActions: "observed" })
const values = []
const x = mobx.observable({ a: 10 })
mobx.reaction(() => x.a, v => values.push(v), { fireImmediately: true })
const f1 = actionAsync(async () => {
if (x.a <= 0) return
x.a -= await task(delay(10, 1))
await task(f1())
})
await f1()
expect(values).toEqual([10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
expectNoActionsRunning()
})
test("it should support recursive async (without task)", async () => {
mobx.configure({ enforceActions: "observed" })
const values = []
const x = mobx.observable({ a: 10 })
mobx.reaction(() => x.a, v => values.push(v), { fireImmediately: true })
const f1 = actionAsync(async () => {
if (x.a <= 0) return
x.a -= await task(delay(10, 1))
await f1()
})
await f1()
expect(values).toEqual([10, 0])
expectNoActionsRunning()
})
test("it should support parallel async", async () => {
mobx.configure({ enforceActions: "observed" })
const values = []
const x = mobx.observable({ a: 1 })
mobx.reaction(() => x.a, v => values.push(v), { fireImmediately: true })
const f1 = actionAsync(async () => {
x.a = 2
x.a = await task(delay(20, 5))
x.a = await task(delay(40, 7))
})
const f2 = actionAsync(async () => {
x.a = 3
x.a = await task(delay(10, 4))
x.a = await task(delay(30, 6))
})
await Promise.all([
f1(),
f2(),
async () => {
expectNoActionsRunning()
},
delayFn(5, expectNoActionsRunning),
delayFn(15, expectNoActionsRunning),
delayFn(25, expectNoActionsRunning),
delayFn(35, expectNoActionsRunning),
delayFn(45, expectNoActionsRunning)
])
expect(values).toEqual([1, 2, 3, 4, 5, 6, 7])
expectNoActionsRunning()
})