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
98 lines (85 loc) · 3.47 KB
/
Copy pathflow.ts
File metadata and controls
98 lines (85 loc) · 3.47 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
import { action, fail, noop } from "../internal"
let generatorId = 0
export type CancellablePromise<T> = Promise<T> & { cancel(): void }
export interface FlowIterator<T> {
next(value?: any): IteratorResult<T> | Promise<IteratorResult<T>>
return?(value?: any): IteratorResult<T> | Promise<IteratorResult<T>>
throw?(e?: any): IteratorResult<T> | Promise<IteratorResult<T>>
}
export function flow<T, U extends any[]>(
generator: (...args: U) => FlowIterator<any>
): (...args: U) => CancellablePromise<T>
export function flow(generator: Function): Function {
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 res = new Promise(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
res.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 res
}
}
function cancelPromise(promise) {
if (typeof promise.cancel === "function") promise.cancel()
}