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