forked from babel/babel
/
async.js
110 lines (97 loc) 路 3.4 KB
/
async.js
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
// @flow
import gensync, { type Gensync, type Handler } from "gensync";
type MaybePromise<T> = T | Promise<T>;
const id = x => x;
const runGenerator = gensync(function* (item) {
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<T, Args: any[]>(
fn: (...args: Args) => T,
message: string,
): Gensync<Args, T> {
return gensync({
sync(...args) {
const result = fn.apply(this, args);
if (isThenable(result)) throw new Error(message);
return result;
},
async(...args) {
return Promise.resolve(fn.apply(this, args));
},
});
}
const withKind = (gensync<[any], any>({
sync: cb => cb("sync"),
async: cb => cb("async"),
}): <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<ActionArgs: mixed[], ActionReturn, Return>(
action: (...args: ActionArgs) => Handler<ActionReturn>,
cb: (
adapted: (...args: ActionArgs) => MaybePromise<ActionReturn>,
) => MaybePromise<Return>,
): Handler<Return> {
const g = gensync<ActionArgs, ActionReturn>(action);
return withKind<Return>(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<[any, any], 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();
}
},
}): <T>(gen: Generator<*, T, *>, cb: Function) => Handler<T>);
// Wait for the given promise to be resolved
export const waitFor = (gensync<[any], any>({
sync: id,
async: id,
}): <T>(p: T | Promise<T>) => Handler<T>);
export function isThenable(val: mixed): boolean %checks {
return (
/*:: val instanceof Promise && */
!!val &&
(typeof val === "object" || typeof val === "function") &&
!!val.then &&
typeof val.then === "function"
);
}