File
Blob: src/workerd/jsg/promise-test.c++
| 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 | |
| 7 | namespace workerd::jsg::test { |
| 8 | namespace { |
| 9 | |
| 10 | V8System v8System; |
| 11 | |
| 12 | int promiseTestResult = 0; |
| 13 | kj::String catchTestResult; |
| 14 | |
| 15 | struct 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 | }; |
| 118 | JSG_DECLARE_ISOLATE_TYPE(PromiseIsolate, PromiseContext); |
| 119 | |
| 120 | KJ_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 | |
| 134 | KJ_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 | |
| 173 | KJ_TEST("whenResolved") { |
| 174 | Evaluator<PromiseContext, PromiseIsolate> e(v8System); |
| 175 | |
| 176 | e.expectEval("whenResolved(Promise.resolve(1))", "undefined", "undefined"); |
| 177 | } |
| 178 | |
| 179 | KJ_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 |