Skip to content
File

Blob: src/workerd/jsg/promise.h

cpp830 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#pragma once
6 
7#include "jsg.h"
8#include "util.h"
9#include "wrappable.h"
10 
11#include <v8-function.h>
12#include <v8-promise.h>
13 
14#include <kj/async.h>
15#include <kj/table.h>
16 
17namespace workerd::jsg {
18 
19// =======================================================================================
20// Utilities for wrapping arbitrary C++ in an opaque way. See wrapOpaque().
21//
22// At present this is used privately in the Promise implementation, but we could consider making
23// wrapOpaque() more public if it is useful.
24 
25template <typename T, bool = isGcVisitable<T>()>
26struct OpaqueWrappable;
27 
28struct OpaqueWrappableBase: public Wrappable {
29 kj::StringPtr jsgGetMemoryName() const override final {
30 return "OpaqueWrappable"_kjc;
31 }
32 void jsgGetMemoryInfo(MemoryTracker& tracker) const override final {
33 Wrappable::jsgGetMemoryInfo(tracker);
34 }
35};
36 
37template <typename T>
38struct OpaqueWrappable<T, false>: public OpaqueWrappableBase {
39 // Used to implement wrapOpaque().
40 
41 OpaqueWrappable(T&& value): value(kj::mv(value)) {}
42 
43 T value;
44 bool movedAway = false;
45 
46 size_t jsgGetMemorySelfSize() const override final {
47 return sizeof(OpaqueWrappable);
48 }
49};
50 
51template <typename T>
52struct OpaqueWrappable<T, true>: public OpaqueWrappable<T, false> {
53 // When T is GC-visitable, make sure to implement visitation.
54 
55 using OpaqueWrappable<T, false>::OpaqueWrappable;
56 
57 void jsgVisitForGc(GcVisitor& visitor) override {
58 if (!this->movedAway) {
59 visitor.visit(this->value);
60 }
61 }
62};
63 
64// Create a JavaScript value that wraps `t` in an opaque way. JS code will see this as an empty
65// object, as if created by `{}`, but C++ code can unwrap the handle with `unwrapOpaque()`.
66//
67// If `T` is a type that can be passed to GcVisitor::visit(), then it will be visited whenever
68// the opaque handle is found to be reachable.
69//
70// Generally, the opaque handle should not actually be passed to the application at all. This
71// is useful in cases where the producer and consumer are both C++ code, but V8 requires that
72// a handle be used for some reason. For example, this is used to pass C++ values through V8
73// Promises.
74//
75// Opaque-wrapping of `V8Ref<T>` is explicitly disallowed to avoid waste. Just use the handle
76// directly in this case. If you really want to wrap a V8Ref opaquely, wrap it in a struct of
77// your own first. (Don't forget to implement `visitForGc()`.)
78template <typename T>
79v8::Local<v8::Value> wrapOpaque(v8::Local<v8::Context> context, T&& t) {
80 static_assert(!kj::isReference<T>());
81 static_assert(!isV8Ref<T>(), "no need to opaque-wrap regular JavaScript values");
82 static_assert(!isV8Local<T>(), "can't opaque-wrap non-persistent handles");
83 
84 auto wrapped = kj::refcounted<OpaqueWrappable<T>>(kj::mv(t));
85 return wrapped->attachOpaqueWrapper(context, isGcVisitable<T>());
86}
87 
88// Unwraps a handle created using `wrapOpaque()`. This consumes (moves away) the underlying
89// value, so can only be called once. Throws if the handle is the wrong type or has already been
90// consumed previously.
91template <typename T>
92T unwrapOpaque(v8::Isolate* isolate, v8::Local<v8::Value> handle) {
93 static_assert(!kj::isReference<T>());
94 static_assert(!isV8Ref<T>(), "no need to opaque-wrap regular JavaScript values");
95 static_assert(!isV8Local<T>(), "can't opaque-wrap non-persistent handles");
96 
97 Wrappable& wrappable = KJ_ASSERT_NONNULL(Wrappable::tryUnwrapOpaque(isolate, handle));
98 OpaqueWrappable<T>* holder = dynamic_cast<OpaqueWrappable<T>*>(&wrappable);
99 KJ_ASSERT(holder != nullptr);
100 KJ_ASSERT(!holder->movedAway);
101 holder->movedAway = true;
102 return kj::mv(holder->value);
103}
104 
105// Unwraps a handle created using `wrapOpaque()`, without consuming the value. Throws if the
106// handle is the wrong type or has already been consumed previously.
107template <typename T>
108T& unwrapOpaqueRef(v8::Isolate* isolate, v8::Local<v8::Value> handle) {
109 static_assert(!kj::isReference<T>());
110 static_assert(!isV8Ref<T>(), "no need to opaque-wrap regular JavaScript values");
111 static_assert(!isV8Local<T>(), "can't opaque-wrap non-persistent handles");
112 
113 Wrappable& wrappable = KJ_ASSERT_NONNULL(Wrappable::tryUnwrapOpaque(isolate, handle));
114 OpaqueWrappable<T>* holder = dynamic_cast<OpaqueWrappable<T>*>(&wrappable);
115 KJ_ASSERT(holder != nullptr);
116 KJ_ASSERT(!holder->movedAway);
117 return holder->value;
118}
119 
120// Destroys the value contained by an opaque handle, without returning it. This is equivalent
121// to calling unwrapOpaque<T>() and dropping the result, except that if the handle is the wrong
122// type, this function silently does nothing rather than throw.
123template <typename T>
124void dropOpaque(v8::Isolate* isolate, v8::Local<v8::Value> handle) {
125 static_assert(!kj::isReference<T>());
126 static_assert(!isV8Ref<T>());
127 
128 KJ_IF_SOME(wrappable, Wrappable::tryUnwrapOpaque(isolate, handle)) {
129 OpaqueWrappable<T>* holder = dynamic_cast<OpaqueWrappable<T>*>(&wrappable);
130 if (holder != nullptr) {
131 holder->movedAway = true;
132 auto drop KJ_UNUSED = kj::mv(holder->value);
133 }
134 }
135}
136 
137// =======================================================================================
138// Promise implementation
139 
140// This type (opaque-wrapped) is the type of the "data" for a continuation callback. We have both
141// the success and error callbacks share the same "data" object so that both underlying C++
142// callbacks are proactively destroyed after one of the runs. Otherwise, we'd only destroy the
143// function that was called, while the other one would have to wait for GC, which may mean
144// keeping around C++ resources longer than necessary.
145template <typename ThenFunc, typename CatchFunc>
146struct ThenCatchPair {
147 ThenFunc thenFunc;
148 CatchFunc catchFunc;
149};
150 
151// FunctionCallback implementing a C++ .then() continuation on a JS promise.
152//
153// We expect the input is already an opaque-wrapped value, args.Data() is an opaque-wrapped C++
154// function to execute, and we want to produce an opaque-wrapped output or Promise.
155template <typename FuncPairType, bool isCatch, typename Input, typename Output>
156void promiseContinuation(const v8::FunctionCallbackInfo<v8::Value>& args) {
157 liftKj(args, [&]() {
158 auto isolate = args.GetIsolate();
159#ifdef KJ_DEBUG
160 // In debug mode only, we verify that the function hasn't captured any KJ heap objects without
161 // a IoOwn. We don't bother with this check in release mode because it's pretty deterministic,
162 // so it's likely to be caught in debug, and we'd like to avoid the extra overhead in releases.
163 DISALLOW_KJ_IO_DESTRUCTORS_SCOPE;
164#endif
165 auto funcPair = unwrapOpaque<FuncPairType>(isolate, args.Data());
166#ifdef KJ_DEBUG
167 kj::AllowAsyncDestructorsScope allowAsyncDestructors;
168#endif
169 auto callFunc = [&]() -> Output {
170 auto& js = Lock::from(isolate);
171 if constexpr (isCatch) {
172 // Exception from V8 is not expected to be opaque-wrapped. It's just a Value.
173 return funcPair.catchFunc(js, Value(isolate, args[0]));
174 } else if constexpr (isVoid<Input>()) {
175 return funcPair.thenFunc(js);
176 } else if constexpr (isV8Ref<Input>()) {
177 return funcPair.thenFunc(js, Input(isolate, args[0]));
178 } else {
179 return funcPair.thenFunc(js, unwrapOpaque<Input>(isolate, args[0]));
180 }
181 };
182 if constexpr (isVoid<Output>()) {
183 callFunc();
184 } else if constexpr (isPromise<Output>()) {
185 // Continuation returns Promise. We don't want to opaque-wrap that, we want to return it
186 // raw, so that the V8 Promise machinery will chain it.
187 
188 // We cast the return value to v8::Local<v8::Value> so that it doesn't trigger liftKj()'s
189 // special handling of promises, where it tries to catch exceptions and merge them into the
190 // promise. We don't need to do this, because this is being called as a .then() which already
191 // catches exceptions and does the right thing.
192 return v8::Local<v8::Value>(callFunc().consumeHandle(Lock::from(isolate)));
193 } else if constexpr (isV8Ref<Output>()) {
194 return callFunc().getHandle(isolate);
195 } else {
196 return wrapOpaque(isolate->GetCurrentContext(), callFunc());
197 }
198 });
199}
200 
201// Promise continuation that propagates the value or exception unmodified, but makes sure to
202// proactively destroy the ThenCatchPair.
203template <typename FuncPairType, bool isCatch>
204void identityPromiseContinuation(const v8::FunctionCallbackInfo<v8::Value>& args) {
205 auto isolate = args.GetIsolate();
206 dropOpaque<FuncPairType>(isolate, args.Data());
207 if constexpr (isCatch) {
208 isolate->ThrowException(args[0]);
209 } else {
210 args.GetReturnValue().Set(args[0]);
211 }
212}
213 
214template <typename TypeWrapper>
215class PromiseWrapper;
216 
217template <typename T>
218class Promise {
219 public:
220 static_assert(!kj::canConvert<T*, v8::Data*>(),
221 "jsg::Promise<T> expects T to be an instantiable C++ type, not a JS heap type; use "
222 "jsg::Promise<jsg::V8Ref<T>> to represent a promise for a JavaScript heap object.");
223 
224 Promise(v8::Isolate* isolate, v8::Local<v8::Promise> v8Promise)
225 : v8Promise(V8Ref<v8::Promise>(isolate, v8Promise)) {}
226 
227 Promise(decltype(nullptr)): v8Promise(kj::none) {}
228 // For use when you're declaring a local variable that will be initialized later.
229 
230 void markAsHandled(Lock& js) {
231 auto promise = getInner(js);
232 promise->MarkAsHandled();
233 markedAsHandled = true;
234 }
235 
236 // Attach a continuation function and error handler to be called when this promise
237 // is fulfilled. It is important to remember that then(...) can synchronously throw
238 // a JavaScript exception (and jsg::JsExceptionThrown) in certain cases.
239 template <typename Func, typename ErrorFunc>
240 PromiseForResult<Func, T, true> then(Lock& js, Func&& func, ErrorFunc&& errorFunc) {
241 using Output = ReturnType<Func, T, true>;
242 static_assert(kj::isSameType<Output, ReturnType<ErrorFunc, Value, true>>(),
243 "functions passed to .then() must return exactly the same type");
244 
245 using FuncPair = ThenCatchPair<Func, ErrorFunc>;
246 return thenImpl<Output>(js, FuncPair{kj::fwd<Func>(func), kj::fwd<ErrorFunc>(errorFunc)},
247 &promiseContinuation<FuncPair, false, T, Output>,
248 &promiseContinuation<FuncPair, true, Value, Output>);
249 }
250 
251 // Attach a continuation function to be called when this promise is fulfilled.
252 // It is important to remember that then(...) can synchronously throw
253 // a JavaScript exception (and jsg::JsExceptionThrown) in certain cases.
254 template <typename Func>
255 PromiseForResult<Func, T, true> then(Lock& js, Func&& func) {
256 using Output = ReturnType<Func, T, true>;
257 
258 // HACK: The error function is never called, so it need not actually be a functor.
259 using FuncPair = ThenCatchPair<Func, bool>;
260 return thenImpl<Output>(js, FuncPair{kj::fwd<Func>(func), false},
261 &promiseContinuation<FuncPair, false, T, Output>,
262 &identityPromiseContinuation<FuncPair, true>);
263 }
264 
265 template <typename ErrorFunc>
266 Promise<T> catch_(Lock& js, ErrorFunc&& errorFunc) {
267 static_assert(kj::isSameType<T, ReturnType<ErrorFunc, Value, true>>(),
268 "function passed to .catch_() must return exactly the promise's type");
269 
270 // HACK: The non-error function is never called, so it need not actually be a functor.
271 using FuncPair = ThenCatchPair<bool, ErrorFunc>;
272 return thenImpl<T>(js, FuncPair{false, kj::fwd<ErrorFunc>(errorFunc)},
273 &identityPromiseContinuation<FuncPair, false>,
274 &promiseContinuation<FuncPair, true, Value, T>);
275 }
276 
277 // whenResolved returns a new Promise<void> that resolves when this promise resolves,
278 // stopping the propagation of the resolved value. Unlike then(), calling whenResolved()
279 // does not consume the promise, and whenResolved() can be called multiple times,
280 // with each call creating a new branch off the original promise. Another key difference
281 // with whenResolved() is that the markAsHandled status will propagate to the new Promise<void>
282 // returned by whenResolved().
283 Promise<void> whenResolved(Lock& js) {
284 auto promise = Promise<void>(js.v8Isolate, getInner(js));
285 if (markedAsHandled) {
286 promise.markAsHandled(js);
287 }
288 return kj::mv(promise);
289 }
290 
291 v8::Local<v8::Promise> consumeHandle(Lock& js) {
292 auto result = getInner(js);
293 v8Promise = kj::none;
294 return result;
295 }
296 
297 // If the promise is resolved, return the result, consuming the Promise. If it is pending
298 // or rejected, returns null. This can be used as an optimization or in tests, but you must
299 // never rely on it for correctness.
300 kj::Maybe<T> tryConsumeResolved(Lock& js) {
301 return js.withinHandleScope([&]() -> kj::Maybe<T> {
302 auto handle =
303 KJ_REQUIRE_NONNULL(v8Promise, "jsg::Promise can only be used once").getHandle(js);
304 switch (handle->State()) {
305 case v8::Promise::kPending:
306 case v8::Promise::kRejected:
307 return kj::none;
308 case v8::Promise::kFulfilled:
309 v8Promise = kj::none;
310 return unwrapOpaque<T>(js.v8Isolate, handle->Result());
311 }
312 });
313 }
314 
315 class Resolver {
316 public:
317 Resolver(v8::Isolate* isolate, v8::Local<v8::Promise::Resolver> v8Resolver)
318 : v8Resolver(isolate, kj::mv(v8Resolver)) {}
319 
320 template <typename U = T, typename = kj::EnableIf<!isVoid<U>()>>
321 void resolve(Lock& js, kj::NoInfer<U>&& value) {
322 js.withinHandleScope([&] {
323 auto context = js.v8Context();
324 v8::Local<v8::Value> handle;
325 if constexpr (isV8Ref<U>()) {
326 handle = value.getHandle(js);
327 } else {
328 handle = wrapOpaque(context, kj::mv(value));
329 }
330 check(v8Resolver.getHandle(js)->Resolve(context, handle));
331 });
332 }
333 
334 template <typename U = T, typename = kj::EnableIf<isVoid<U>()>>
335 void resolve(Lock& js) {
336 js.withinHandleScope(
337 [&] { check(v8Resolver.getHandle(js)->Resolve(js.v8Context(), js.v8Undefined())); });
338 }
339 
340 void resolve(Lock& js, Promise&& promise) {
341 // Resolve to another Promise.
342 check(v8Resolver.getHandle(js)->Resolve(js.v8Context(), promise.consumeHandle(js)));
343 }
344 
345 void reject(Lock& js, v8::Local<v8::Value> exception) {
346 js.withinHandleScope(
347 [&] { check(v8Resolver.getHandle(js)->Reject(js.v8Context(), exception)); });
348 }
349 
350 void reject(Lock& js, kj::Exception exception, ExceptionToJsOptions options = {}) {
351 reject(js, exceptionToJs(js.v8Isolate, kj::mv(exception), options));
352 }
353 
354 Resolver addRef(Lock& js) {
355 return {js.v8Isolate, v8Resolver.getHandle(js)};
356 }
357 void visitForGc(GcVisitor& visitor) {
358 visitor.visit(v8Resolver);
359 }
360 
361 JSG_MEMORY_INFO(Resolver) {
362 tracker.trackField("resolver", v8Resolver);
363 }
364 
365 private:
366 V8Ref<v8::Promise::Resolver> v8Resolver;
367 friend class MemoryTracker;
368 };
369 
370 void visitForGc(GcVisitor& visitor) {
371 visitor.visit(v8Promise);
372 }
373 
374 JSG_MEMORY_INFO(Promise) {
375 KJ_IF_SOME(promise, v8Promise) {
376 tracker.trackField("promise", promise);
377 }
378 }
379 
380 // Ths is for testing/diagnostics purposes only.
381 enum class State {
382 PENDING = v8::Promise::kPending,
383 FULFILLED = v8::Promise::kFulfilled,
384 REJECTED = v8::Promise::kRejected,
385 CONSUMED = 3 // Not a real state; indicates the Promise has been consumed.
386 };
387 State getState(Lock& js) {
388 KJ_IF_SOME(promise, v8Promise) {
389 return static_cast<State>(promise.getHandle(js)->State());
390 } else {
391 return State::CONSUMED;
392 }
393 }
394 
395 private:
396 kj::Maybe<V8Ref<v8::Promise>> v8Promise;
397 bool markedAsHandled = false;
398 
399 v8::Local<v8::Promise> getInner(Lock& js) {
400 return KJ_REQUIRE_NONNULL(v8Promise, "jsg::Promise can only be used once").getHandle(js);
401 }
402 
403 template <typename U = T, typename = kj::EnableIf<!isVoid<U>()>()>
404 Promise(Lock& js, kj::NoInfer<U>&& value) {
405 js.withinHandleScope([&] {
406 auto context = js.v8Context();
407 auto resolver = check(v8::Promise::Resolver::New(context));
408 v8::Local<v8::Value> handle;
409 if constexpr (isV8Ref<U>()) {
410 handle = value.getHandle(js);
411 } else {
412 handle = wrapOpaque(context, kj::mv(value));
413 };
414 check(resolver->Resolve(context, handle));
415 v8Promise.emplace(js.v8Isolate, resolver->GetPromise());
416 });
417 }
418 
419 template <typename U = T, typename = kj::EnableIf<isVoid<U>()>()>
420 explicit Promise(Lock& js) {
421 js.withinHandleScope([&] {
422 auto context = js.v8Context();
423 auto resolver = check(v8::Promise::Resolver::New(context));
424 check(resolver->Resolve(context, js.v8Undefined()));
425 v8Promise.emplace(js.v8Isolate, resolver->GetPromise());
426 });
427 }
428 
429 template <typename Result, typename FuncPair>
430 MaintainPromise<Result> thenImpl(Lock& js,
431 FuncPair&& funcPair,
432 v8::FunctionCallback thenCallback,
433 v8::FunctionCallback errCallback) {
434 return js.withinHandleScope([&] {
435 auto context = js.v8Context();
436 
437 auto funcPairHandle = wrapOpaque(context, kj::mv(funcPair));
438 
439 auto then = check(v8::Function::New(
440 context, thenCallback, funcPairHandle, 1, v8::ConstructorBehavior::kThrow));
441 
442 auto errThen = check(v8::Function::New(
443 context, errCallback, funcPairHandle, 1, v8::ConstructorBehavior::kThrow));
444 
445 return MaintainPromise<Result>(
446 js.v8Isolate, check(consumeHandle(js)->Then(context, then, errThen)));
447 });
448 }
449 
450 friend class Lock;
451 template <typename TypeWrapper>
452 friend class PromiseWrapper;
453 friend class MemoryTracker;
454};
455 
456template <typename T>
457class Promise<Promise<T>> {
458 static_assert(sizeof(T*) == 0, "Promise<Promise<T>> is invalid; use Promise<T> instead");
459};
460 
461template <typename T>
462class Promise<kj::Promise<T>> {
463 static_assert(sizeof(T*) == 0, "jsg::Promise<kj::Promise<T>> is illegal; you need a IoOwn!");
464};
465 
466template <typename T>
467struct PromiseResolverPair {
468 Promise<T> promise;
469 Promise<T>::Resolver resolver;
470 
471 JSG_MEMORY_INFO(PromiseResolverPair) {
472 tracker.trackField("promise", promise);
473 tracker.trackField("resolver", resolver);
474 }
475};
476 
477template <typename T>
478PromiseResolverPair<T> Lock::newPromiseAndResolver() {
479 return withinHandleScope([&]() -> PromiseResolverPair<T> {
480 auto resolver = check(v8::Promise::Resolver::New(v8Context()));
481 auto promise = resolver->GetPromise();
482 return {{v8Isolate, promise}, {v8Isolate, resolver}};
483 });
484}
485 
486template <typename T>
487inline Promise<T> Lock::resolvedPromise(T&& value) {
488 return Promise<T>(*this, kj::fwd<T>(value));
489}
490inline Promise<void> Lock::resolvedPromise() {
491 return Promise<void>(*this);
492}
493 
494template <typename T>
495Promise<T> Lock::rejectedPromise(v8::Local<v8::Value> exception) {
496 auto [promise, resolver] = newPromiseAndResolver<T>();
497 resolver.reject(*this, exception);
498 return kj::mv(promise);
499}
500 
501template <typename T>
502Promise<T> Lock::rejectedPromise(jsg::Value exception) {
503 return withinHandleScope([&] { return rejectedPromise<T>(exception.getHandle(*this)); });
504}
505 
506template <typename T>
507Promise<T> Lock::rejectedPromise(kj::Exception&& exception, ExceptionToJsOptions options) {
508 return withinHandleScope(
509 [&] { return rejectedPromise<T>(exceptionToJs(kj::mv(exception), options)); });
510}
511 
512template <class Func>
513PromiseForResult<Func, void, false> Lock::evalNow(Func&& func) {
514 using Result = RemovePromise<ReturnType<Func, void>>;
515 v8::TryCatch tryCatch(v8Isolate);
516 try {
517 if constexpr (isPromise<ReturnType<Func, void>>()) {
518 return func();
519 } else {
520 return resolvedPromise<Result>(func());
521 }
522 } catch (jsg::JsExceptionThrown&) {
523 if (tryCatch.HasCaught() && tryCatch.CanContinue()) {
524 return rejectedPromise<Result>(tryCatch.Exception());
525 } else {
526 // Probably TerminateExecution() called.
527 tryCatch.ReThrow();
528 throw;
529 }
530 } catch (kj::Exception& e) {
531 return rejectedPromise<Result>(kj::mv(e));
532 } catch (std::exception& exception) {
533 return rejectedPromise<Result>(makeInternalError(v8Isolate, exception.what()));
534 } catch (...) {
535 return rejectedPromise<Result>(makeInternalError(
536 v8Isolate, kj::str("caught unknown exception of type: ", kj::getCaughtExceptionType())));
537 }
538}
539 
540// -----------------------------------------------------------------------------
541 
542// Continuation function that converts a promised C++ value into a JavaScript value.
543template <typename TypeWrapper, typename Input>
544void thenWrap(const v8::FunctionCallbackInfo<v8::Value>& args) {
545 if constexpr (isVoid<Input>()) {
546 // No wrapping needed. Note that we still attach `thenWrap` to the promise chain only because
547 // we use `args.data` to prevent the object from being GC'ed while the promise is still
548 // executing.
549 args.GetReturnValue().SetUndefined();
550 } else if constexpr (isV8Ref<Input>()) {
551 // Similarly, no unwrapping needed.
552 args.GetReturnValue().Set(args[0]);
553 } else {
554 liftKj(args, [&]() {
555 v8::Isolate* isolate = args.GetIsolate();
556 auto& wrapper = TypeWrapper::from(isolate);
557 auto context = isolate->GetCurrentContext();
558 auto& lock = Lock::from(isolate);
559 return wrapper.wrap(lock, context, kj::none, unwrapOpaque<Input>(isolate, args[0]));
560 });
561 }
562}
563 
564// Continuation function that converts a promised JavaScript value into a C++ value.
565template <typename TypeWrapper, typename Output>
566void thenUnwrap(const v8::FunctionCallbackInfo<v8::Value>& args) {
567 liftKj(args, [&]() {
568 v8::Isolate* isolate = args.GetIsolate();
569 auto& wrapper = TypeWrapper::from(isolate);
570 auto context = isolate->GetCurrentContext();
571 auto& js = Lock::from(isolate);
572 return wrapOpaque(context,
573 wrapper.template unwrap<Output>(
574 js, context, args[0], TypeErrorContext::promiseResolution()));
575 });
576}
577 
578// TypeWrapper mixin for Promise.
579template <typename TypeWrapper>
580class PromiseWrapper {
581 public:
582 // The constructor here is a bit of a hack. The config is optional and might not be a JsgConfig
583 // object (or convertible to a JsgConfig) if is provided. However, because of the way TypeWrapper
584 // inherits PromiseWrapper, we always end up passing a config option (which might be
585 // std::nullptr_t). The getConfig allows us to handle any case using reasonable defaults.
586 PromiseWrapper(const auto& config): config(getConfig(config)) {}
587 
588 template <typename T>
589 static constexpr const char* getName(Promise<T>*) {
590 return "Promise";
591 }
592 
593 template <typename T>
594 v8::Local<v8::Promise> wrap(jsg::Lock& js,
595 v8::Local<v8::Context> context,
596 kj::Maybe<v8::Local<v8::Object>> creator,
597 Promise<T>&& promise) {
598 // Add a .then() to unwrap the value (i.e. convert C++ value to JavaScript).
599 //
600 // We use `creator` as the `data` value for this continuation so that the creator object
601 // cannot be GC'ed while the callback still exists. This gives us the KJ-style guarantee that
602 // the object whose method returned the promise will not be destroyed while the promise is
603 // still executing.
604 auto markedAsHandled = promise.markedAsHandled;
605 auto then = check(v8::Function::New(context, &thenWrap<TypeWrapper, T>, creator.orDefault({}),
606 1, v8::ConstructorBehavior::kThrow));
607 auto ret = check(promise.consumeHandle(js)->Then(context, then));
608 // Although we added a .then() to the promise to translate the value to JavaScript, we would
609 // like things to behave as if the C++ code returned this Promise directly to JavaScript. In
610 // particular, if the C++ code marked the Promise handled, then the derived JavaScript promise
611 // ought to be marked as handled as well.
612 if (markedAsHandled) {
613 ret->MarkAsHandled();
614 }
615 
616 return ret;
617 }
618 
619 template <typename T>
620 kj::Maybe<Promise<T>> tryUnwrap(Lock& js,
621 v8::Local<v8::Context> context,
622 v8::Local<v8::Value> handle,
623 Promise<T>*,
624 kj::Maybe<v8::Local<v8::Object>> parentObject) {
625 if (handle->IsPromise()) {
626 auto promise = handle.As<v8::Promise>();
627 if constexpr (!isVoid<T>() && !isV8Ref<T>()) {
628 // Add a .then() to unwrap the promise's resolution (i.e. convert it from JS to C++).
629 // Note that we don't need to handle the rejection case here as there is no wrapping
630 // applied to exception values, so we just let it propagate through.
631 //
632 // TODO(perf): We could in theory check if promise->State() is kFulfilled and, in that
633 // case, pull out promise->Result(), unwrap it, and make a new immediate promise.
634 // Similarly in `wrap()`. Not clear if the added complexity is worth it, though.
635 auto then = check(v8::Function::New(
636 context, &thenUnwrap<TypeWrapper, T>, {}, 1, v8::ConstructorBehavior::kThrow));
637 promise = check(promise->Then(context, then));
638 }
639 return Promise<T>(js.v8Isolate, promise);
640 } else {
641 // Input is a resolved value (not a promise). Try to unwrap it now.
642 
643 // If the input is an object that is not a promise, there's a chance it is a custom
644 // thenable (and object with a then method intended to be used as a promise). If that
645 // is the case, then we can handle the thenable by resolving it to a promise then
646 // unwrapping that promise.
647 // Unfortunately this needs to be gated by a compatibility flag because there are
648 // existing workers that appear to rely on the old behavior -- although it's not clear
649 // if those workers actually work the way they were intended to.
650 if (config.unwrapCustomThenables && isThenable(context, handle)) {
651 auto paf = check(v8::Promise::Resolver::New(context));
652 check(paf->Resolve(context, handle));
653 return tryUnwrap(
654 js, context, paf->GetPromise(), static_cast<Promise<T>*>(nullptr), parentObject);
655 }
656 
657 if constexpr (isVoid<T>()) {
658 // When expecting Promise<void>, we treat absolutely any non-promise value as being
659 // an immediately-resolved promise. This is consistent with JavaScript where you'd
660 // commonly use `Promise.resolve(param).then(() => {...})` in order to coerce the param
661 // to a promise... normally you wouldn't bother checking that the param specifically
662 // resolved to `undefined`, you'd just throw away whatever it resolved to.
663 //
664 // It's possible to argue that we should actually allow only `undefined` here but
665 // changing it now could break existing users, e.g. html-rewriter.ew-test is broken
666 // because it writes `() => someExpression()` for a callback that's supposed to
667 // optionally return Promise<void> -- it seems like the callback isn't actually intending
668 // to return the result of `someExpression()` but does so by accident since the braces
669 // are missing. This is probably common in user code, too.
670 return js.resolvedPromise();
671 } else {
672 auto& wrapper = *static_cast<TypeWrapper*>(this);
673 KJ_IF_SOME(value, wrapper.tryUnwrap(js, context, handle, (T*)nullptr, parentObject)) {
674 return js.resolvedPromise(kj::mv(value));
675 } else {
676 // Wrong type.
677 return kj::none;
678 }
679 }
680 }
681 }
682 
683 private:
684 const JsgConfig config;
685 
686 static bool isThenable(v8::Local<v8::Context> context, v8::Local<v8::Value> handle) {
687 if (handle->IsObject()) {
688 auto obj = handle.As<v8::Object>();
689 return check(obj->Has(context, v8StrIntern(v8::Isolate::GetCurrent(), "then")));
690 }
691 return false;
692 }
693};
694 
695// -----------------------------------------------------------------------------
696 
697// A utility used internally by ServiceWorkerGlobalScope to perform the book keeping
698// for unhandled promise rejection notifications. The handler maintains a table of
699// weak references to rejected promises that have not been handled and will handle
700// emitting events and console warnings as appropriate.
701class UnhandledRejectionHandler {
702 public:
703 using Handler = void(jsg::Lock& js,
704 v8::PromiseRejectEvent event,
705 jsg::V8Ref<v8::Promise> promise,
706 jsg::Value value);
707 
708 explicit UnhandledRejectionHandler(kj::Function<Handler> handler): handler(kj::mv(handler)) {}
709 
710 void report(jsg::Lock& js,
711 v8::PromiseRejectEvent event,
712 jsg::V8Ref<v8::Promise> promise,
713 jsg::Value value);
714 
715 void setUseMicrotasksCompletedCallback(bool value) {
716 useMicrotasksCompletedCallback = value;
717 }
718 
719 void clear();
720 
721 JSG_MEMORY_INFO(UnhandledRejectionHandler) {
722 // TODO (soon): Can we reasonably measure the function handler?
723 tracker.trackField("unhandledRejections", unhandledRejections);
724 tracker.trackField("warnedRejections", warnedRejections);
725 }
726 
727 private:
728 // Used as part of the book keeping for unhandled rejections. When an
729 // unhandled rejection occurs, the unhandledRejections Table will be updated.
730 // If the rejection is later handled asynchronously, then the item will be
731 // removed from the table. When the unhandled rejection table is processed
732 // later in the event loop tick, any remaining rejections will generate a
733 // warning to the inspector console (if enabled);
734 struct UnhandledRejection {
735 explicit UnhandledRejection(jsg::Lock& js,
736 jsg::V8Ref<v8::Promise> promise,
737 jsg::Value value,
738 v8::Local<v8::Message> message);
739 
740 ~UnhandledRejection();
741 
742 UnhandledRejection(UnhandledRejection&& other) = default;
743 UnhandledRejection& operator=(UnhandledRejection&& other) = default;
744 
745 // TODO(cleanup): It would be better to use a jsg::HashableV8Ref or
746 // jsg::Identity here but we need the Globals to always be weak so
747 // that the book keeping doesn't end up being a memory leak.
748 
749 uint hash;
750 
751 // We use v8::Globals directly here because these references are going to
752 // be made weak and could be garbage collected and cleared while the items
753 // are still in the unhandledRejections or warnedRejections tables.
754 
755 v8::Global<v8::Promise> promise;
756 v8::Global<v8::Value> value;
757 v8::Global<v8::Message> message;
758 kj::Maybe<Ref<AsyncContextFrame>> asyncContextFrame;
759 
760 inline bool isAlive() {
761 return !promise.IsEmpty() && !value.IsEmpty();
762 }
763 
764 uint hashCode() const {
765 return hash;
766 }
767 
768 JSG_MEMORY_INFO(UnhandledRejection) {
769 tracker.trackField("promise", promise);
770 tracker.trackField("value", value);
771 visitForMemoryInfo(tracker);
772 }
773 void visitForMemoryInfo(MemoryTracker& tracker) const;
774 };
775 
776 // A v8::Promise with memoized hash code.
777 struct HashedPromise {
778 v8::Local<v8::Promise> promise;
779 uint hash;
780 
781 HashedPromise(v8::Local<v8::Promise> promise)
782 : promise(promise),
783 hash(kj::hashCode(promise->GetIdentityHash())) {}
784 
785 JSG_MEMORY_INFO(HashedPromise) {
786 tracker.trackField("promise", promise);
787 }
788 };
789 
790 struct UnhandledRejectionCallbacks {
791 inline const UnhandledRejection& keyForRow(
792 const UnhandledRejection& row KJ_LIFETIMEBOUND) const {
793 return row;
794 }
795 inline bool matches(const UnhandledRejection& a, const UnhandledRejection& b) const {
796 return a.promise == b.promise;
797 }
798 inline bool matches(const UnhandledRejection& a, const HashedPromise& b) const {
799 return a.promise == b.promise;
800 }
801 inline uint hashCode(const UnhandledRejection& row) const {
802 return row.hashCode();
803 }
804 inline uint hashCode(const HashedPromise& key) const {
805 return key.hash;
806 }
807 };
808 
809 kj::Function<Handler> handler;
810 bool scheduled = false;
811 // Controlled by the unhandled_rejection_after_microtask_checkpoint compat flag.
812 bool useMicrotasksCompletedCallback = false;
813 
814 using UnhandledRejectionsTable =
815 kj::Table<UnhandledRejection, kj::HashIndex<UnhandledRejectionCallbacks>>;
816 
817 UnhandledRejectionsTable unhandledRejections;
818 UnhandledRejectionsTable warnedRejections;
819 
820 void rejectedWithNoHandler(jsg::Lock& js, jsg::V8Ref<v8::Promise> promise, jsg::Value value);
821 void handledAfterRejection(jsg::Lock& js, jsg::V8Ref<v8::Promise> promise);
822 void ensureProcessingWarnings(jsg::Lock& js);
823 void processWarnings(jsg::Lock& js);
824 
825 // Must be static: V8 requires a plain C function pointer for this callback.
826 static void onMicrotasksCompleted(v8::Isolate* isolate, void* data);
827};
828 
829} // namespace workerd::jsg