/
async.ts
182 lines (173 loc) · 5.13 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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import { Result, ok, error, isOk, isError } from "./result";
/**
* A Result, wrapped in a promise. The promise should never be rejected,
* and should always resolve to an Ok or Error.
*/
export type ResultP<OkData, ErrorMessage> = Promise<
Result<OkData, ErrorMessage>
>;
// Solves an error with returning ResultP from an async function.
// tslint:disable-next-line:variable-name
export const ResultP = Promise;
/**
* Chains together async operations that may succeed or fail
*
* Takes a Result and a mapping function.
* If the result is an Ok, applies the function to the data.
* If the result is an Error, passes the Result through unchanged.
*
* @param f - the function to run on the ok data
* @param result - The result to match against
* @returns - The Result from function f or the Error
*
* ```javascript
* pipeA
* (fetchUser())
* (okChainAsync(user => fetchComments(user.id)))
* (okDo(comments => console.log('total comments:', comments.length))
* .value
* ```
*/
export function okChainAsync<OkData, OkOutput, ErrorOutput>(
f: (ok: OkData) => ResultP<OkOutput, ErrorOutput>
) {
return async function<ErrorMessage>(
result: Result<OkData, ErrorMessage>
): ResultP<OkOutput, ErrorMessage | ErrorOutput> {
if (isError(result)) {
return result;
}
return f(result.ok);
};
}
/**
* Chains together async operations that may succeed or fail
*
* Takes a Result and a mapping function.
* If the result is an Error, applies the function to the data and returns the new promise-wrapped result.
* If the result is an Ok, passes the Result through unchanged.
*
* @param f - the function to run on the error message
* @param result - The result to match against
* @returns - The Result from function f or the Ok result
*
* ```javascript
* const fetchData = () => fetch("https://example.com/data");
* pipeA
* (getCachedData)
* (errorRescueAsync(fetchData))
* (okThen(transformData))
* .value
* ```
*/
export function errorRescueAsync<ErrorMessage, OkOutput, ErrorOutput>(
f: (ok: ErrorMessage) => ResultP<OkOutput, ErrorOutput>
) {
return async function<OkData>(
result: Result<OkData, ErrorMessage>
): ResultP<OkData | OkOutput, ErrorOutput> {
if (isOk(result)) {
return result;
}
return f(result.error);
};
}
/**
* Runs a function for side effects on the payload, only if the result is Ok.
*
* @param f - the async function to run on the ok data
* @param result - The result to match against
* @returns A promise of the the original result
*
* ```javascript
* pipeA(
* (fetchData)
* // Saves to cache, and awaits completion before moving on.
* (okDoAsync(saveToCache))
* (okThen(transformData))
* ```
*/
export function okDoAsync<OkData>(f: (ok: OkData) => any) {
return async function<ErrorMessage>(
result: Result<OkData, ErrorMessage>
): ResultP<OkData, ErrorMessage> {
if (isOk(result)) {
await f(result.ok);
}
return result;
};
}
/**
* Runs a function for side effects on the payload, only if the result is Error. Waits for the side effect to complete before returning.
*
* @param f - the function to run on the error message
* @param result - The result to match against
* @returns a promise of the original Result
*
* ```javascript
* pipeA(
* (fetchData)
* // Logs an error, and awaits completion before moving on.
* (errorDoAsync(logError))
* (okThen(transformData))
* ```
*/
export function errorDoAsync<ErrorMessage>(f: (ok: ErrorMessage) => any) {
return async function<OkData>(
result: Result<OkData, ErrorMessage>
): ResultP<OkData, ErrorMessage> {
if (isError(result)) {
await f(result.error);
}
return result;
};
}
/**
* Awaits a promise, and returns a result based on the outcome
*
* Note that the Error case isn't typesafe, since promises don't have an
* error type, just a success one. It's probably a good idea to use a
* `errorThen` or `errorReplace` to handle errors and soon as possible.
*
* @param promise
* @returns Ok if the promise resolved, Error if it was rejected.
*
* ```javascript
* pipeA(
* (promiseToResult(fetch("http://example.com/data")))
* (errorThen(handleError))
* (okThen(transformData))
* ```
*/
export function promiseToResult<OkData>(
promise: Promise<OkData>
): ResultP<OkData, any> {
return promise.then(ok).catch(error);
}
/**
* Converts a function that returns a promise to one that always resolved to a Result
*
* Note that the Error case isn't typesafe, since promises don't have an
* error type, just a success one. It's probably a good idea to use a
* `errorThen` or `errorReplace` to handle errors and soon as possible.
*
* @param f A function that returns a promise
* @returns a function that returns a promise that always resolves to a Result
*
* ```javascript
* const writeFile = resultify(promisify(fs.writeFile));
*
* pipeA(
* (writeFile("path/to/file"))
* (errorThen(handleError))
* (okThen(transformFileData))
* )
* ```
*/
export function resultify<Args extends any[], OkData>(
f: (...args: Args) => Promise<OkData>
) {
return function(...args: Args): ResultP<OkData, any> {
return promiseToResult(f(...args));
};
}