Skip to content
File

Blob: src/workerd/jsg/promise-test.c++

5.3 KB
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#include "jsg-test.h"
6 
7namespace workerd::jsg::test {
8namespace {
9 
10V8System v8System;
11 
12int promiseTestResult = 0;
13kj::String catchTestResult;
14 
15struct PromiseContext: public jsg::Object, public jsg::ContextGlobal {
16 Promise<kj::String> makePromise(jsg::Lock& js) {
17 auto [p, r] = js.newPromiseAndResolver<int>();
18 resolver = kj::mv(r);
19 return p.then(js, [](jsg::Lock&, int i) { return i * 2; })
20 .then(js, [](jsg::Lock& js, int i) {
21 return js.resolvedPromise(i + 2);
22 }).then(js, [](jsg::Lock& js, int i) { return kj::str(i); });
23 }
24 
25 void resolvePromise(Lock& js, int i) {
26 KJ_ASSERT_NONNULL(resolver).resolve(js, kj::mv(i));
27 }
28 
29 void setResult(jsg::Lock& js, Promise<kj::String> promise) {
30 // Throwing away the result of `.then()` doesn't cancel it!
31 promise.then(js, [](jsg::Lock&, kj::String str) {
32 promiseTestResult = str.parseAs<int>();
33 }).then(js, [](jsg::Lock&) { promiseTestResult += 60000; });
34 }
35 
36 void catchIt(jsg::Lock& js, Promise<int> promise) {
37 promise
38 .catch_(js,
39 [](jsg::Lock& js, Value value) -> int {
40 JSG_FAIL_REQUIRE(Error, kj::str(value.getHandle(js)));
41 })
42 .then(js, [](jsg::Lock& js, int i) {
43 KJ_FAIL_REQUIRE("shouldn't get here");
44 return kj::str("nope");
45 }, [](jsg::Lock& js, Value value) {
46 return kj::str(value.getHandle(js));
47 }).then(js, [](jsg::Lock&, kj::String s) { catchTestResult = kj::mv(s); });
48 }
49 
50 Promise<kj::String> makeRejected(jsg::Lock& js, jsg::Value exception) {
51 return js.rejectedPromise<kj::String>(kj::mv(exception));
52 }
53 
54 Promise<kj::String> makeRejectedKj(jsg::Lock& js) {
55 return js.rejectedPromise<kj::String>(JSG_KJ_EXCEPTION(FAILED, TypeError, "bar"));
56 }
57 
58 void testConsumeResolved(jsg::Lock& js) {
59 auto [promise, resolver] = js.newPromiseAndResolver<int>();
60 KJ_EXPECT(promise.tryConsumeResolved(js) == kj::none);
61 resolver.resolve(js, 123);
62 KJ_EXPECT(KJ_ASSERT_NONNULL(promise.tryConsumeResolved(js)) == 123);
63 
64 KJ_EXPECT(
65 js.rejectedPromise<kj::String>(v8StrIntern(js.v8Isolate, "foo")).tryConsumeResolved(js) ==
66 kj::none);
67 }
68 
69 void whenResolved(jsg::Lock& js, jsg::Promise<int> promise) {
70 // The returned promise should resolve to undefined.
71 
72 uint resolved = 0;
73 
74 auto handle = promise.whenResolved(js).then(js, [&resolved](jsg::Lock&) {
75 resolved++;
76 }).consumeHandle(js);
77 
78 promise.then(js, [&resolved](jsg::Lock&, int v) {
79 KJ_ASSERT(v == 1);
80 resolved++;
81 });
82 
83 js.runMicrotasks();
84 KJ_ASSERT(resolved == 2);
85 
86 {
87 KJ_ASSERT(handle->State() == v8::Promise::PromiseState::kFulfilled);
88 auto result = handle->Result();
89 KJ_ASSERT(!result.IsEmpty());
90 KJ_ASSERT(result->IsUndefined());
91 }
92 }
93 
94 int thenable(jsg::Lock& js, jsg::Promise<int> promise) {
95 int result = 0;
96 promise.then(js, [&result](jsg::Lock& js, int val) { result = val; });
97 js.runMicrotasks();
98 return result;
99 }
100 
101 JSG_RESOURCE_TYPE(PromiseContext) {
102 JSG_READONLY_PROTOTYPE_PROPERTY(promise, makePromise);
103 JSG_METHOD(resolvePromise);
104 JSG_METHOD(setResult);
105 JSG_METHOD(catchIt);
106 
107 JSG_METHOD(makeRejected);
108 JSG_METHOD(makeRejectedKj);
109 
110 JSG_METHOD(testConsumeResolved);
111 JSG_METHOD(whenResolved);
112 
113 JSG_METHOD(thenable);
114 }
115 
116 kj::Maybe<Promise<int>::Resolver> resolver;
117};
118JSG_DECLARE_ISOLATE_TYPE(PromiseIsolate, PromiseContext);
119 
120KJ_TEST("jsg::Promise<T>") {
121 Evaluator<PromiseContext, PromiseIsolate> e(v8System);
122 
123 e.expectEval("setResult(promise.then(i => i + 1 /* oops, i is a string */));\n"
124 "resolvePromise(123)",
125 "undefined", "undefined");
126 
127 KJ_EXPECT(promiseTestResult == 0);
128 
129 e.runMicrotasks();
130 
131 KJ_EXPECT(promiseTestResult == 62481);
132}
133 
134KJ_TEST("jsg::Promise<T> exception catching") {
135 Evaluator<PromiseContext, PromiseIsolate> e(v8System);
136 
137 {
138 e.expectEval("catchIt(Promise.reject('foo'))", "undefined", "undefined");
139 
140 KJ_EXPECT(catchTestResult == nullptr);
141 
142 e.runMicrotasks();
143 
144 KJ_EXPECT(catchTestResult == "Error: foo");
145 catchTestResult = nullptr;
146 }
147 
148 {
149 e.expectEval("catchIt(makeRejected(123))", "undefined", "undefined");
150 
151 KJ_EXPECT(catchTestResult == nullptr);
152 
153 e.runMicrotasks();
154 
155 KJ_EXPECT(catchTestResult == "Error: 123");
156 catchTestResult = nullptr;
157 }
158 
159 {
160 e.expectEval("catchIt(makeRejectedKj())", "undefined", "undefined");
161 
162 KJ_EXPECT(catchTestResult == nullptr);
163 
164 e.runMicrotasks();
165 
166 KJ_EXPECT(catchTestResult == "Error: TypeError: bar");
167 catchTestResult = nullptr;
168 }
169 
170 { e.expectEval("testConsumeResolved()", "undefined", "undefined"); }
171}
172 
173KJ_TEST("whenResolved") {
174 Evaluator<PromiseContext, PromiseIsolate> e(v8System);
175 
176 e.expectEval("whenResolved(Promise.resolve(1))", "undefined", "undefined");
177}
178 
179KJ_TEST("thenable") {
180 static const auto config = JsgConfig{
181 .unwrapCustomThenables = true,
182 };
183 
184 struct ThenableConfig {
185 operator const JsgConfig&() const {
186 return config;
187 }
188 };
189 
190 Evaluator<PromiseContext, PromiseIsolate, ThenableConfig> e(v8System);
191 
192 e.expectEval("thenable({ then(res) { res(123) } })", "number", "123");
193}
194 
195} // namespace
196} // namespace workerd::jsg::test