1 /*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/js_promise.h"
17
18 #include "ecmascript/builtins/builtins_promise_handler.h"
19 #include "ecmascript/global_env.h"
20 #include "ecmascript/interpreter/interpreter.h"
21 #include "ecmascript/jobs/micro_job_queue.h"
22
23 namespace panda::ecmascript {
24 using BuiltinsPromiseHandler = builtins::BuiltinsPromiseHandler;
25
CreateResolvingFunctions(JSThread * thread,const JSHandle<JSPromise> & promise)26 JSHandle<ResolvingFunctionsRecord> JSPromise::CreateResolvingFunctions(JSThread *thread,
27 const JSHandle<JSPromise> &promise)
28 {
29 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
30 // 1. Let alreadyResolved be a new Record { [[value]]: false }.
31 JSHandle<PromiseRecord> record = factory->NewPromiseRecord();
32 record->SetValue(thread, JSTaggedValue::False());
33
34 // 2. Let resolve be a new built-in function object as defined in Promise Resolve Functions (25.4.1.3.2).
35 JSHandle<JSPromiseReactionsFunction> resolve = factory->CreateJSPromiseReactionsFunction(
36 MethodIndex::BUILTINS_PROMISE_HANDLER_RESOLVE);
37 // 3. Set the [[Promise]] internal slot of resolve to promise.
38 resolve->SetPromise(thread, promise);
39 // 4. Set the [[AlreadyResolved]] internal slot of resolve to alreadyResolved.
40 resolve->SetAlreadyResolved(thread, record);
41 // 5. Let reject be a new built-in function object as defined in Promise Reject Functions (25.4.1.3.1).
42 JSHandle<JSPromiseReactionsFunction> reject = factory->CreateJSPromiseReactionsFunction(
43 MethodIndex::BUILTINS_PROMISE_HANDLER_REJECT);
44 // 6. Set the [[Promise]] internal slot of reject to promise.
45 reject->SetPromise(thread, promise);
46 // 7. Set the [[AlreadyResolved]] internal slot of reject to alreadyResolved.
47 reject->SetAlreadyResolved(thread, record);
48 // 8. Return a new Record { [[Resolve]]: resolve, [[Reject]]: reject }.
49 JSHandle<ResolvingFunctionsRecord> reactions = factory->NewResolvingFunctionsRecord();
50 reactions->SetResolveFunction(thread, resolve.GetTaggedValue());
51 reactions->SetRejectFunction(thread, reject.GetTaggedValue());
52 return reactions;
53 }
54
FulfillPromise(JSThread * thread,const JSHandle<JSPromise> & promise,const JSHandle<JSTaggedValue> & value)55 JSTaggedValue JSPromise::FulfillPromise(JSThread *thread, const JSHandle<JSPromise> &promise,
56 const JSHandle<JSTaggedValue> &value)
57 {
58 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
59 // 1. Assert: the value of promise's [[PromiseState]] internal slot is "pending".
60 ASSERT_PRINT(promise->GetPromiseState() == PromiseState::PENDING, "FulfillPromise: state must be pending");
61 // 2. Let reactions be the value of promise's [[PromiseFulfillReactions]] internal slot.
62 JSHandle<TaggedQueue> reactions(thread, promise->GetPromiseFulfillReactions());
63 // 3. Set the value of promise's [[PromiseResult]] internal slot to value.
64 promise->SetPromiseResult(thread, value);
65 // 4. Set the value of promise's [[PromiseFulfillReactions]] internal slot to undefined.
66 promise->SetPromiseFulfillReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
67 // 5. Set the value of promise's [[PromiseRejectReactions]] internal slot to undefined.
68 promise->SetPromiseRejectReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
69 // 6. Set the value of promise's [[PromiseState]] internal slot to "fulfilled".
70 promise->SetPromiseState(PromiseState::FULFILLED);
71 // 7. Return TriggerPromiseReactions(reactions, reason).
72 return TriggerPromiseReactions(thread, reactions, value);
73 }
74
NewPromiseCapability(JSThread * thread,const JSHandle<JSTaggedValue> & obj)75 JSHandle<PromiseCapability> JSPromise::NewPromiseCapability(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
76 {
77 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
78 // 1. If IsConstructor(C) is false, throw a TypeError exception.
79 if (!obj->IsConstructor()) {
80 THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: obj is not constructor!",
81 factory->NewPromiseCapability());
82 }
83 // 2. NOTE C is assumed to be a constructor function that supports the parameter conventions of the Promise
84 // constructor (see 25.4.3.1).
85 // 3. Let promiseCapability be a new PromiseCapability { [[Promise]]: undefined, [[Resolve]]: undefined,
86 // [[Reject]]: undefined }.
87 JSHandle<PromiseCapability> promiseCapability = factory->NewPromiseCapability();
88 // 4. Let executor be a new built-in function object as defined in GetCapabilitiesExecutor Functions
89 // (25.4.1.5.1).
90 JSHandle<JSPromiseExecutorFunction> executor = factory->CreateJSPromiseExecutorFunction();
91 // 5. Set the [[Capability]] internal slot of executor to promiseCapability.
92 executor->SetCapability(thread, promiseCapability.GetTaggedValue());
93 // 6. Let promise be Construct(C, «executor»).
94 // 7. ReturnIfAbrupt(promise).
95 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
96 EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, obj, undefined, undefined, 1);
97 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, factory->NewPromiseCapability());
98 info->SetCallArg(executor.GetTaggedValue());
99 JSTaggedValue result = JSFunction::Construct(info);
100 JSHandle<JSPromise> promise(thread, result);
101 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, factory->NewPromiseCapability());
102 // 8. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
103 if (!promiseCapability->GetResolve().IsCallable()) {
104 THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: resolve is not a callable function!",
105 factory->NewPromiseCapability());
106 }
107 // 9. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception.
108 if (!promiseCapability->GetReject().IsCallable()) {
109 THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: reject is not a callable function!",
110 factory->NewPromiseCapability());
111 }
112 // 10. Set promiseCapability.[[Promise]] to promise.
113 promiseCapability->SetPromise(thread, promise);
114 // 11. Return promiseCapability.
115 return promiseCapability;
116 }
117
IsPromise(const JSHandle<JSTaggedValue> & value)118 bool JSPromise::IsPromise(const JSHandle<JSTaggedValue> &value)
119 {
120 // 1. If Type(x) is not Object, return false.
121 if (!value->IsECMAObject()) {
122 return false;
123 }
124 // 2. If x does not have a [[PromiseState]] internal slot, return false.
125 if (!value->IsJSPromise()) {
126 return false;
127 }
128 // 3. Return true
129 return true;
130 }
131
RejectPromise(JSThread * thread,const JSHandle<JSPromise> & promise,const JSHandle<JSTaggedValue> & reason)132 JSTaggedValue JSPromise::RejectPromise(JSThread *thread, const JSHandle<JSPromise> &promise,
133 const JSHandle<JSTaggedValue> &reason)
134 {
135 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
136 // 1. Assert: the value of promise's [[PromiseState]] internal slot is "pending".
137 ASSERT_PRINT(promise->GetPromiseState() == PromiseState::PENDING, "RejectPromise: state must be pending");
138 // 2. Let reactions be the value of promise's [[PromiseRejectReactions]] internal slot.
139 JSHandle<TaggedQueue> reactions(thread, TaggedQueue::Cast(promise->GetPromiseRejectReactions().GetTaggedObject()));
140 // 3. Set the value of promise's [[PromiseResult]] internal slot to reason.
141 promise->SetPromiseResult(thread, reason);
142 // 4. Set the value of promise's [[PromiseFulfillReactions]] internal slot to undefined.
143 promise->SetPromiseFulfillReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
144 // 5. Set the value of promise's [[PromiseRejectReactions]] internal slot to undefined.
145 promise->SetPromiseRejectReactions(thread, globalConst->GetHandledUndefined(), SKIP_BARRIER);
146 // 6. Set the value of promise's [[PromiseState]] internal slot to "rejected".
147 promise->SetPromiseState(PromiseState::REJECTED);
148 // 7. When a promise is rejected without any handlers, it is called with its operation argument set to "reject".
149 if (!promise->GetPromiseIsHandled()) {
150 thread->GetCurrentEcmaContext()->PromiseRejectionTracker(promise, reason, PromiseRejectionEvent::REJECT);
151 }
152 // 8. Return TriggerPromiseReactions(reactions, reason).
153 return TriggerPromiseReactions(thread, reactions, reason);
154 }
155
TriggerPromiseReactions(JSThread * thread,const JSHandle<TaggedQueue> & reactions,const JSHandle<JSTaggedValue> & argument)156 JSTaggedValue JSPromise::TriggerPromiseReactions(JSThread *thread, const JSHandle<TaggedQueue> &reactions,
157 const JSHandle<JSTaggedValue> &argument)
158 {
159 // 1. Repeat for each reaction in reactions, in original insertion order
160 // a. Perform EnqueueJob("PromiseJobs", PromiseReactionJob, «reaction, argument»).
161 JSHandle<job::MicroJobQueue> job = thread->GetCurrentEcmaContext()->GetMicroJobQueue();
162 JSHandle<GlobalEnv> globalEnv = thread->GetEcmaVM()->GetGlobalEnv();
163 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
164 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
165 JSHandle<JSFunction> promiseReactionsJob(globalEnv->GetPromiseReactionJob());
166 JSMutableHandle<PromiseReaction> reaction(thread, JSTaggedValue::Undefined());
167 while (!reactions->Empty()) {
168 reaction.Update(reactions->Pop(thread));
169 JSHandle<TaggedArray> arguments = factory->NewTaggedArray(2); // 2 means the length of new array
170 arguments->Set(thread, 0, reaction);
171 arguments->Set(thread, 1, argument);
172 job::MicroJobQueue::EnqueueJob(thread, job, job::QueueType::QUEUE_PROMISE, promiseReactionsJob, arguments);
173 }
174 // 2. Return undefined.
175 return globalConst->GetUndefined();
176 }
177
IfThrowGetThrowValue(JSThread * thread)178 JSHandle<JSTaggedValue> JSPromise::IfThrowGetThrowValue(JSThread *thread)
179 {
180 return JSHandle<JSTaggedValue>(thread, thread->GetException());
181 }
182 } // namespace panda::ecmascript
183