-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathJSLikeBasePromise.hpp
399 lines (341 loc) · 14 KB
/
JSLikeBasePromise.hpp
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
#pragma once
#include <iostream>
#include <coroutine>
#include <memory>
#include <functional>
#include <exception>
namespace JSLike {
using namespace std;
// Forward declarations needed to declare "friendships" among structs.
struct BasePromise;
struct PromiseAllState;
struct PromiseAll;
struct PromiseAny;
template<typename T> struct Promise;
// Forward declaration for dynamic_cast
template<typename T> struct PromiseState;
/**
* JSLike::bad_cast specializes std:bad_cast by allowing the the caller to specify
* a "what" message at construction time.
*/
class bad_cast : public std::bad_cast {
private:
std::string m_message;
public:
bad_cast(const std::string& message) : m_message(message) {}
// Override what() to provide custom error message
const char* what() const noexcept override {
return m_message.c_str();
}
};
/**
* A BasePromiseState, is maintains the state of a BasePromise in the data members
* described below. It also implements the related behavior described below:
*
* - The flag m_isResolved indicates whether the BasePromise has been resolved or not.
* - If the BasePromise is being used as a "thenable" (e.g. a "Lambda" was specified
* via a call to Then()), then m_thenVoidLambda points to the Lambda.
* - If the BasePromise is being co_awaited on by a coroutine, then the BasePromise
* state also includes the coroutine handle m_h<>, which the resolve() method uses to
* resume the coroutine.
* - To handle scenarios in which the BasePromise is rejected, the state also includes
* an exception_ptr named m_eptr whose value is specified via a call to reject().
* If the BasePromise is being awaited on by a coroutine (e.g. via a
* BasePromise::awaiter_type), then the exception_ptr is rethrown after the
* coroutine resumes execution (see BasePromise::awaiter_type::await_resume()).
* This allows the coroutine to handle the exception if it wants.
* - But if the BasePromise is being used as a "thenable", which results in a
* call to a user-supplied "Catch" Lambda when the BasePromise is rejected, then the
* state also includes a pointer m_catchCallback to the "Catch" Lambda. When the
* BasePromise is rejected, the "Catch" Lambda is called, and the exception_ptr is
* passed to it as an argument. The "Catch" Lambda may rethrow the exception_ptr,
* catch the resulting exception, and handle it if it wants.
*/
struct BasePromiseState : public enable_shared_from_this<BasePromiseState> {
typedef function<void(shared_ptr<BasePromiseState>)> ThenCallback;
typedef function<void(exception_ptr)> CatchCallback;
BasePromiseState() : m_isResolved(false) {}
// Don't allow copies of any kind. Always use std::shared_ptr to instances of this type.
BasePromiseState(const BasePromiseState&) = delete;
BasePromiseState(BasePromiseState&& other) = delete;
BasePromiseState& operator=(const BasePromiseState&) = delete;
BasePromiseState& operator=(BasePromiseState&&) = delete;
virtual ~BasePromiseState() {} // Force BasePromiseState to be polymorphic to support dynamic_cast<>.
/**
* Resolve the BasePromise, and resume the coroutine or execute the "Then" Lambda.
*/
void resolve() {
resolve(shared_from_this());
}
void resolve(shared_ptr<BasePromiseState> state) {
if (m_eptr || m_isResolved) return; // prevent multiple rejection/resolution
m_isResolved = true;
if (m_h.address() != nullptr) {
m_h();
}
else if (m_thenCallback) {
m_thenCallback(state);
}
}
bool isResolved() {
return m_isResolved;
}
bool isRejected() {
return m_eptr != nullptr;
}
template<typename T>
bool isValueOfType() {
PromiseState<T>* castState = dynamic_cast<PromiseState<T> *>(this);
return (castState != nullptr);
}
/**
* Get the value of a PromiseState<T>. Use this method when you know that your BasePromiseState is
* a PromiseState<T>. This allows you to store references to PromiseState<T>s as BasePromiseStates,
* and to access them as PromiseState<T>s.
*
* @param value The value to which to PromiseState<T> was resolved. If the PromiseState<T> was not
* resolvedd, then the returned value is undefined.
*/
template<typename T>
T &value() {
PromiseState<T>* castState = dynamic_cast<PromiseState<T> *>(this);
if (!castState) {
string err("bad cast from BasePromiseState to PromiseState<");
err += typeid(T).name();
err += ">";
throw bad_cast(err.c_str());
}
return castState->value();
}
// TODO: Add UT that tests PreresolvedCopy
template<typename T>
void resolve(T& value) {
PromiseState<T>* castState = dynamic_cast<PromiseState<T> *>(this);
if (!castState) {
string err("bad cast from BasePromiseState to PromiseState<");
err += typeid(T).name();
err += ">";
throw bad_cast(err.c_str());
}
castState->resolve(value);
}
// TODO: Add UT that tests PreresolvedMove and PreresolvedLiteral
/**
* Resolve a PromiseState<T>. Use this method when you know that your BasePromiseState is a
* PromiseState<T>. This allows you to store references to PromiseState<T>s as BasePromiseStates,
* and to operate on them as PromiseState<T>s.
*
* @param value The value to which to resolve the PromiseState.
*/
template<typename T>
void resolve(T && value) {
PromiseState<T>* castState = dynamic_cast<PromiseState<T> *>(this);
if (!castState) {
string err("bad cast from BasePromiseState to PromiseState<");
err += typeid(T).name();
err += ">";
throw bad_cast(err.c_str());
}
castState->resolve(forward<T>(value));
}
/**
* Reject the BasePromise with the specified exception_ptr. There are two
* posibilities for what happens next:
* 1) If a coroutine references a BasePromise::awaiter_type that references this
* BasePromiseState, then this method resumes the coroutine, which then calls
* BasePromise::awaiter_type::await_resume(), which then throws the specified
* exception_ptr so that the coroutine may catch/handle it.
* 2) If a function (either regular or coroutine) references a BasePromise that
* references this BasePromiseState, then this method invokes the Lambda
* specified via BasePromise::Catch() passing it the specified exception_ptr.
* The Lambda may then throw the exception_ptr in order to catch/handle the
* exception.
*
* @param eptr An exception_ptr that points to the exception with which to reject
* the BasePromise.
*/
void reject(exception_ptr eptr) {
if (m_eptr || m_isResolved) return; // prevent multiple rejection/resolution
m_eptr = eptr;
// Resume so that the calling coroutine will call the awaiter_type's await_resume(),
// which will rethrow eptr
if (m_h.address() != nullptr) {
// This code executes if a coroutine references a BasePromise::awaiter_type that
// references this BasePromiseState.
// Resume the coroutine so that its call to BasePromise::awaiter_type::await_resume()
// will throw the specified exception.
m_h();
}
else if (m_catchCallback) {
// This code executes if a regular function (or a coroutine) references a BasePromise
// that references this BasePromiseState.
// Invoke the Lambda specified via BasePromise::Catch().
m_catchCallback(eptr);
}
}
/**
* Rethrow the exception_ptr specified when/if reject() was called. This method is called
* by various awaiters (e.g.BasePromise::awaiter_type, and Promise<T>::awaiter_type) from
* their await_resume() after the awaiting coroutine is resumed so that the exception
* is rethrown in the context of the coroutine (e.g. so that it can handle the exception).
*/
void rethrowIfRejected() {
if (m_eptr)
rethrow_exception(m_eptr);
}
protected:
friend struct BasePromise;
friend struct Promise<void>;
friend struct PromiseAllState;
friend struct PromiseAll;
friend struct PromiseAnyState;
friend struct PromiseAny;
virtual void Then(ThenCallback thenCallback) {
m_thenCallback = thenCallback;
if (m_isResolved) {
thenCallback(shared_from_this());
}
}
void Catch(CatchCallback catchCallback) {
m_catchCallback = catchCallback;
if (m_eptr) {
catchCallback(m_eptr);
}
}
// Why beat around the bush. Do both at the same time.
void Then(ThenCallback thenCallback, CatchCallback catchCallback) {
Then(thenCallback);
Catch(catchCallback);
}
ThenCallback m_thenCallback;
mutable coroutine_handle<> m_h;
bool m_isResolved;
CatchCallback m_catchCallback;
exception_ptr m_eptr;
};
/**
* A BasePromise represents the eventual completion (or failure) of an asynchronous
* operation. A BasePromise does not resolve into a value, although the type Promise<T> does.
* A BasePromise is just a thin veneer around a BasePromiseState. It implements the types
* needed to interoperate with coroutines (e.g. awiter_type and promise_type) as inner classes,
* which are also thin veneers around the same BasePromiseState. This design allows the
* BasePromise, BasePromise::promise_type, and BasePromise::awaiter_type to be easily copied/moved.
*/
struct BasePromise {
protected:
BasePromise() = default;
public:
typedef function<void(shared_ptr<BasePromiseState>)> TaskInitializer;
typedef BasePromiseState::ThenCallback ThenCallback;
typedef BasePromiseState::CatchCallback CatchCallback;
BasePromise(TaskInitializer initializer)
{
initializer(state());
}
// The state of the BasePromise is constructed by this BasePromise and it is shared with a promise_type or
// an awaiter_type, and with a "then" Lambda to resolve/reject the BasePromise.
shared_ptr<BasePromiseState> m_state;
shared_ptr<BasePromiseState> state() {
if(!m_state) m_state = make_shared<BasePromiseState>();
return m_state;
}
bool isResolved() {
return state()->isResolved();
}
bool isRejected() {
return state()->isRejected();
}
BasePromise Then(ThenCallback thenCallback) {
BasePromise chainedPromise;
auto chainedPromiseState = chainedPromise.state();
state()->Then([chainedPromiseState, thenCallback](auto resolvedState)
{
thenCallback(resolvedState);
chainedPromiseState->resolve(resolvedState);
});
return chainedPromise;
}
BasePromise Catch(CatchCallback catchCallback) {
BasePromise chainedPromise;
auto chainedPromiseState = chainedPromise.state();
state()->Catch([chainedPromiseState, catchCallback](auto ex)
{
catchCallback(ex);
chainedPromiseState->reject(ex);
});
return chainedPromise;
}
BasePromise Then(ThenCallback thenCallback, CatchCallback catchCallback) {
BasePromise chainedPromise;
auto chainedPromiseState = chainedPromise.state();
state()->Then([chainedPromiseState, thenCallback](auto resolvedState)
{
thenCallback(resolvedState);
chainedPromiseState->resolve(resolvedState);
},
[chainedPromiseState, catchCallback](auto ex)
{
catchCallback(ex);
chainedPromiseState->reject(ex);
});
return chainedPromise;
}
/**
* promise_type_base is the base class for promise_type implemented by various Promises derived
* from BasePromise.
*/
struct promise_type_base {
suspend_never initial_suspend() {
return {};
}
suspend_never final_suspend() noexcept {
return {};
}
/**
* This method is called if an exception is thrown in the context of a coroutine, and it is
* not handled by the coroutine. This method essentially "captures" the exception and rejects the
* BasePromise returned by the coroutine.
*/
void unhandled_exception() {
exception_ptr eptr = current_exception();
m_state->reject(eptr);
}
shared_ptr<BasePromiseState> m_state;
};
/**
* awaiter_type_base is the base for awaiter_type implemented by various Promises derived
* from BasePromise.
*/
struct awaiter_type_base {
awaiter_type_base() = delete;
awaiter_type_base(const awaiter_type_base&) = delete;
awaiter_type_base(awaiter_type_base&& other) = default;
awaiter_type_base& operator=(const awaiter_type_base&) = delete;
awaiter_type_base(shared_ptr<BasePromiseState> state) : m_state(state) {}
/**
* Answer the question of whether or not the coroutin calling co_await should suspend.
* This method is called as an optimization in case the coroutine execution should NOT
* be suspended.
*
* @return True if execution should NOT be suspended, otherwise false.
*/
bool await_ready() const noexcept {
return m_state->m_isResolved || m_state->m_eptr != nullptr;
}
/**
* This method is called immediately after the coroutine is suspended (after it called
* co_await). After this method returns, control is transfered back to the caller/resumer
* of the coroutine.
*
* NOTE that we could safely start an async read operation in here.
*
* @param coroutine_handle The coroutine_handle that may be used to resume execution
* of the coroutine.
*/
void await_suspend(coroutine_handle<> h) const noexcept {
m_state->m_h = h;
}
shared_ptr<BasePromiseState> m_state;
};
};
};