-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathresolve-promise-object.ts
136 lines (119 loc) · 3.52 KB
/
resolve-promise-object.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
* @file resolve-promise-object.ts
* @author Stephen Belanger <[email protected]>
* @author Brandon Kalinowski
* @copyright 2020 Brandon Kalinowski (@brandonkal)
* @description Recursively resolve any promises in an object to form a resulting JSON structure.
* @version 1.0.1
* @license MIT
*/
/**
* MPV or Maybe Promise Value is an object where values can
* either be a concrete value or a promise of that value.
*/
export type MPV<T> = {
[P in keyof T]: T[P] extends (infer U)[] ? MPV<U>[]
: T[P] extends object ? MPV<T[P]>
: PromiseLike<T[P]> | T[P];
};
/**
* MPVMap is an object where each key is a MPV
*/
export type MPVMap<T> = { [P in keyof T]: MPV<T[P]> };
/**
* deepResolve takes a value and resolves all keys recursively to their promise-resolved values. Call this function to transform an object of promises and values to just their values.
* deepResolve rejects if any nested promise rejects.
*/
export function deepResolve<T>(
object: MPV<T>,
// deno-lint-ignore ban-types
_callback?: Function,
): Promise<T> {
if (typeof _callback != "function") {
_callback = noop;
}
// If the property is not an object,
// it needs no further processing.
if (object === null || !isObject(object)) {
return _callback(null, object);
}
return new Promise(function (presolve, reject) {
const callback = function (err: any, res: any) {
if (err) {
reject(err);
} else {
presolve(res);
}
_callback!(err, res);
};
// If it is a promise, wait for it to resolve.
// Run the check again to find nested promises.
if (isPromise(object)) {
return (object as any).then(checkAgain.bind(null, null), callback);
}
// If it has a toJSON method, assume it can be used directly.
if (canJSON(object)) {
object = (object as any).toJSON();
if (!isObject(object)) {
return callback(null, object);
}
}
// Build a list of promises and promise-like structures to wait for.
const remains: any[] = [];
Object.keys(object).forEach(function (key) {
//@ts-ignore crazy any
const item = object[key];
if (isPromise(item) || isObject(item)) {
remains.push(key);
}
});
// If none were found, we must be done.
if (!remains.length) {
return callback(null, object);
}
// Otherwise, loop through the list.
let pending = remains.length;
remains.forEach(function (key) {
//@ts-ignore -- crazy any
const item = object[key];
// Promises and queries must be checked again upon success
// to ensure nested promises are properly processed.
if (isPromise(item)) {
item.then(
doneHandler(key, checkAgain).bind(null, null),
doneHandler(key, callback),
);
}
deepResolve(item, doneHandler(key, callback));
});
// All the check to be restarted so we
// can return promises from promises.
function checkAgain(err: any, res: any) {
if (err) return callback(err, undefined);
deepResolve(res, callback);
}
// Promises need to call the restart the check,
// so we use this to allow them to swap out fn.
// deno-lint-ignore ban-types
function doneHandler(key: any, fn: Function) {
return function (err: any, result: any) {
if (err) return callback(err, undefined);
//@ts-ignore crazy any
object[key] = result;
if (--pending === 0) {
fn(null, object);
}
};
}
});
}
function canJSON(v: any) {
return typeof v.toJSON === "function";
}
function isObject(v: any) {
return typeof v === "object";
}
function isPromise(v: any) {
return v && typeof v === "object" && typeof v.then === "function";
}
function noop() {}