-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
/
async.ts
120 lines (108 loc) 路 3.76 KB
/
async.ts
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
114
115
116
117
118
119
120
import gensync from "gensync";
import type { Gensync, Handler } from "gensync";
type MaybePromise<T> = T | Promise<T>;
const id = x => x;
const runGenerator: {
sync<Return>(gen: Generator<unknown, Return>): Return;
async<Return>(gen: Generator<unknown, Return>): Promise<Return>;
errback<Return>(
gen: Generator<unknown, Return>,
cb: (err: Error, val: Return) => void,
): void;
} = gensync<(item: Generator) => any>(function* <Return>(
item: Generator<unknown, Return>,
) {
return yield* item;
});
// This Gensync returns true if the current execution context is
// asynchronous, otherwise it returns false.
export const isAsync = gensync<() => boolean>({
sync: () => false,
errback: cb => cb(null, true),
});
// This function wraps any functions (which could be either synchronous or
// asynchronous) with a Gensync. If the wrapped function returns a promise
// but the current execution context is synchronous, it will throw the
// provided error.
// This is used to handle user-provided functions which could be asynchronous.
export function maybeAsync<Fn extends (...args: any) => any>(
fn: Fn,
message: string,
): Gensync<Fn> {
return gensync({
sync(...args) {
const result = fn.apply(this, args) as ReturnType<Fn>;
if (isThenable(result)) throw new Error(message);
return result;
},
async(...args) {
return Promise.resolve(fn.apply(this, args));
},
});
}
const withKind = gensync<(cb: (kind: "sync" | "async") => any) => any>({
sync: cb => cb("sync"),
async: cb => cb("async"),
}) as <T>(cb: (kind: "sync" | "async") => MaybePromise<T>) => Handler<T>;
// This function wraps a generator (or a Gensync) into another function which,
// when called, will run the provided generator in a sync or async way, depending
// on the execution context where this forwardAsync function is called.
// This is useful, for example, when passing a callback to a function which isn't
// aware of gensync, but it only knows about synchronous and asynchronous functions.
// An example is cache.using, which being exposed to the user must be as simple as
// possible:
// yield* forwardAsync(gensyncFn, wrappedFn =>
// cache.using(x => {
// // Here we don't know about gensync. wrappedFn is a
// // normal sync or async function
// return wrappedFn(x);
// })
// )
export function forwardAsync<
Action extends (...args: unknown[]) => any,
Return,
>(
action: (...args: Parameters<Action>) => Handler<ReturnType<Action>>,
cb: (
adapted: (...args: Parameters<Action>) => MaybePromise<ReturnType<Action>>,
) => MaybePromise<Return>,
): Handler<Return> {
const g = gensync<Action>(action);
return withKind(kind => {
const adapted = g[kind];
return cb(adapted);
});
}
// If the given generator is executed asynchronously, the first time that it
// is paused (i.e. When it yields a gensync generator which can't be run
// synchronously), call the "firstPause" callback.
export const onFirstPause = gensync<(gen: Generator, cb: Function) => any>({
name: "onFirstPause",
arity: 2,
sync: function (item) {
return runGenerator.sync(item);
},
errback: function (item, firstPause, cb) {
let completed = false;
runGenerator.errback(item, (err, value) => {
completed = true;
cb(err, value);
});
if (!completed) {
firstPause();
}
},
}) as <T>(gen: Generator<any, T, any>, cb: Function) => Handler<T>;
// Wait for the given promise to be resolved
export const waitFor = gensync({
sync: id,
async: id,
}) as <T>(p: T | Promise<T>) => Handler<T>;
export function isThenable<T = any>(val: any): val is PromiseLike<T> {
return (
!!val &&
(typeof val === "object" || typeof val === "function") &&
!!val.then &&
typeof val.then === "function"
);
}