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 23namespace panda::ecmascript { 24using BuiltinsPromiseHandler = builtins::BuiltinsPromiseHandler; 25 26JSHandle<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 55JSTaggedValue 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 75JSHandle<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 118bool 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 132JSTaggedValue 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 156JSTaggedValue 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 178JSHandle<JSTaggedValue> JSPromise::IfThrowGetThrowValue(JSThread *thread) 179{ 180 return JSHandle<JSTaggedValue>(thread, thread->GetException()); 181} 182} // namespace panda::ecmascript 183