• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mobxjs / mobx / 6725053580

01 Nov 2023 08:33PM UTC coverage: 91.343% (-0.3%) from 91.676%
6725053580

Pull #3790

github

mweststrate
Merge branch 'main' into Matchlighter-decorators2022-2
Pull Request #3790: [Local fork] TC 39 decorators

1788 of 2213 branches covered (0.0%)

96 of 96 new or added lines in 13 files covered. (100.0%)

3313 of 3627 relevant lines covered (91.34%)

6655.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.97
/packages/mobx/src/api/flow.ts
1
import {
48✔
2
    action,
3
    noop,
4
    die,
5
    isFunction,
6
    Annotation,
7
    isStringish,
8
    storeAnnotation,
9
    createFlowAnnotation,
10
    createDecoratorAnnotation,
11
    is20223Decorator
12
} from "../internal"
13

14
import type { ClassMethodDecorator } from "../types/decorator_fills"
15

16
export const FLOW = "flow"
48✔
17

18
let generatorId = 0
48✔
19

20
export function FlowCancellationError() {
48✔
21
    this.message = "FLOW_CANCELLED"
10✔
22
}
23
FlowCancellationError.prototype = Object.create(Error.prototype)
48✔
24

25
export function isFlowCancellationError(error: Error) {
48✔
26
    return error instanceof FlowCancellationError
2✔
27
}
28

29
export type CancellablePromise<T> = Promise<T> & { cancel(): void }
30

31
interface Flow extends Annotation, PropertyDecorator, ClassMethodDecorator {
32
    <R, Args extends any[]>(
33
        generator: (...args: Args) => Generator<any, R, any> | AsyncGenerator<any, R, any>
34
    ): (...args: Args) => CancellablePromise<R>
35
    bound: Annotation & PropertyDecorator & ClassMethodDecorator
36
}
37

38
const flowAnnotation = createFlowAnnotation("flow")
48✔
39
const flowBoundAnnotation = createFlowAnnotation("flow.bound", { bound: true })
48✔
40

41
export const flow: Flow = Object.assign(
48✔
42
    function flow(arg1, arg2?) {
43
        // @flow (2022.3 Decorators)
44
        if (is20223Decorator(arg2)) {
83!
45
            return flowAnnotation.decorate_20223_(arg1, arg2)
×
46
        }
47
        // @flow
48
        if (isStringish(arg2)) {
83✔
49
            return storeAnnotation(arg1, arg2, flowAnnotation)
7✔
50
        }
51
        // flow(fn)
52
        if (__DEV__ && arguments.length !== 1) {
76!
53
            die(`Flow expects single argument with generator function`)
×
54
        }
55
        const generator = arg1
76✔
56
        const name = generator.name || "<unnamed flow>"
76✔
57

58
        // Implementation based on https://github.com/tj/co/blob/master/index.js
59
        const res = function () {
76✔
60
            const ctx = this
60✔
61
            const args = arguments
60✔
62
            const runId = ++generatorId
60✔
63
            const gen = action(`${name} - runid: ${runId} - init`, generator).apply(ctx, args)
60✔
64
            let rejector: (error: any) => void
65
            let pendingPromise: CancellablePromise<any> | undefined = undefined
60✔
66

67
            const promise = new Promise(function (resolve, reject) {
60✔
68
                let stepId = 0
60✔
69
                rejector = reject
60✔
70

71
                function onFulfilled(res: any) {
72
                    pendingPromise = undefined
88✔
73
                    let ret
74
                    try {
88✔
75
                        ret = action(
88✔
76
                            `${name} - runid: ${runId} - yield ${stepId++}`,
77
                            gen.next
78
                        ).call(gen, res)
79
                    } catch (e) {
80
                        return reject(e)
1✔
81
                    }
82

83
                    next(ret)
87✔
84
                }
85

86
                function onRejected(err: any) {
87
                    pendingPromise = undefined
10✔
88
                    let ret
89
                    try {
10✔
90
                        ret = action(
10✔
91
                            `${name} - runid: ${runId} - yield ${stepId++}`,
92
                            gen.throw!
93
                        ).call(gen, err)
94
                    } catch (e) {
95
                        return reject(e)
3✔
96
                    }
97
                    next(ret)
7✔
98
                }
99

100
                function next(ret: any) {
101
                    if (isFunction(ret?.then)) {
95!
102
                        // an async iterator
103
                        ret.then(next, reject)
2✔
104
                        return
2✔
105
                    }
106
                    if (ret.done) {
93✔
107
                        return resolve(ret.value)
55✔
108
                    }
109
                    pendingPromise = Promise.resolve(ret.value) as any
38✔
110
                    return pendingPromise!.then(onFulfilled, onRejected)
38✔
111
                }
112

113
                onFulfilled(undefined) // kick off the process
60✔
114
            }) as any
115

116
            promise.cancel = action(`${name} - runid: ${runId} - cancel`, function () {
60✔
117
                try {
9✔
118
                    if (pendingPromise) {
9✔
119
                        cancelPromise(pendingPromise)
8✔
120
                    }
121
                    // Finally block can return (or yield) stuff..
122
                    const res = gen.return!(undefined as any)
9✔
123
                    // eat anything that promise would do, it's cancelled!
124
                    const yieldedPromise = Promise.resolve(res.value)
8✔
125
                    yieldedPromise.then(noop, noop)
8✔
126
                    cancelPromise(yieldedPromise) // maybe it can be cancelled :)
8✔
127
                    // reject our original promise
128
                    rejector(new FlowCancellationError())
8✔
129
                } catch (e) {
130
                    rejector(e) // there could be a throwing finally block
1✔
131
                }
132
            })
133
            return promise
60✔
134
        }
135
        res.isMobXFlow = true
76✔
136
        return res
76✔
137
    } as any,
138
    flowAnnotation
139
)
140

141
flow.bound = createDecoratorAnnotation(flowBoundAnnotation)
48✔
142

143
function cancelPromise(promise) {
144
    if (isFunction(promise.cancel)) {
16✔
145
        promise.cancel()
1✔
146
    }
147
}
148

149
export function flowResult<T>(
48✔
150
    result: T
151
): T extends Generator<any, infer R, any>
152
    ? CancellablePromise<R>
153
    : T extends CancellablePromise<any>
154
    ? T
155
    : never {
156
    return result as any // just tricking TypeScript :)
2✔
157
}
158

159
export function isFlow(fn: any): boolean {
48✔
160
    return fn?.isMobXFlow === true
179!
161
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc