forked from mobxjs/mobx
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathflow.ts
More file actions
113 lines (97 loc) · 3.88 KB
/
Copy pathflow.ts
File metadata and controls
113 lines (97 loc) · 3.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import { action, fail, noop } from "../internal"
let generatorId = 0
export type CancellablePromise<T> = Promise<T> & { cancel(): void }
export interface FlowYield {
// fake, only for typing
"!!flowYield": undefined
}
export interface FlowReturn<T> {
// fake, only for typing
"!!flowReturn": T
}
// we skip promises that are the result of yielding promises (except if they use flowReturn)
export type FlowReturnType<R> = IfAllAreFlowYieldThenVoid<
R extends FlowReturn<infer FR>
? FR extends Promise<infer FRP> ? FRP : FR
: R extends Promise<any> ? FlowYield : R
>
// we extract yielded promises from the return type
export type IfAllAreFlowYieldThenVoid<R> = Exclude<R, FlowYield> extends never
? void
: Exclude<R, FlowYield>
export function flow<R, Args extends any[]>(
generator: (...args: Args) => IterableIterator<R>
): (...args: Args) => CancellablePromise<FlowReturnType<R>> {
if (arguments.length !== 1)
fail(process.env.NODE_ENV && `Flow expects one 1 argument and cannot be used as decorator`)
const name = generator.name || "<unnamed flow>"
// Implementation based on https://github.com/tj/co/blob/master/index.js
return function() {
const ctx = this
const args = arguments
const runId = ++generatorId
const gen = action(`${name} - runid: ${runId} - init`, generator).apply(ctx, args)
let rejector: (error: any) => void
let pendingPromise: CancellablePromise<any> | undefined = undefined
const promise = new Promise<R>(function(resolve, reject) {
let stepId = 0
rejector = reject
function onFulfilled(res: any) {
pendingPromise = undefined
let ret
try {
ret = action(`${name} - runid: ${runId} - yield ${stepId++}`, gen.next).call(
gen,
res
)
} catch (e) {
return reject(e)
}
next(ret)
}
function onRejected(err: any) {
pendingPromise = undefined
let ret
try {
ret = action(`${name} - runid: ${runId} - yield ${stepId++}`, gen.throw).call(
gen,
err
)
} catch (e) {
return reject(e)
}
next(ret)
}
function next(ret: any) {
if (ret && typeof ret.then === "function") {
// an async iterator
ret.then(next, reject)
return
}
if (ret.done) return resolve(ret.value)
pendingPromise = Promise.resolve(ret.value) as any
return pendingPromise!.then(onFulfilled, onRejected)
}
onFulfilled(undefined) // kick off the process
}) as any
promise.cancel = action(`${name} - runid: ${runId} - cancel`, function() {
try {
if (pendingPromise) cancelPromise(pendingPromise)
// Finally block can return (or yield) stuff..
const res = gen.return()
// eat anything that promise would do, it's cancelled!
const yieldedPromise = Promise.resolve(res.value)
yieldedPromise.then(noop, noop)
cancelPromise(yieldedPromise) // maybe it can be cancelled :)
// reject our original promise
rejector(new Error("FLOW_CANCELLED"))
} catch (e) {
rejector(e) // there could be a throwing finally block
}
})
return promise as CancellablePromise<FlowReturnType<R>>
}
}
function cancelPromise(promise) {
if (typeof promise.cancel === "function") promise.cancel()
}