* Copyright (c) 2021-2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ecmascript/js_promise.h"
#include "ecmascript/builtins/builtins_promise_handler.h"
#include "ecmascript/global_env.h"
#include "ecmascript/interpreter/interpreter.h"
#include "ecmascript/jobs/micro_job_queue.h"
namespace panda::ecmascript {
using BuiltinsPromiseHandler = builtins::BuiltinsPromiseHandler;
JSHandle<ResolvingFunctionsRecord> JSPromise::CreateResolvingFunctions(JSThread *thread,
const JSHandle<JSPromise> &promise)
{
if (thread->GetEcmaVM()->GetJSOptions().EnablePendingCheak()) {
thread->GetEcmaVM()->InsertAsyncStackTrace(promise);
}
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<PromiseRecord> record = factory->NewPromiseRecord();
record->SetValue(thread, JSTaggedValue::False());
JSHandle<JSPromiseReactionsFunction> resolve = factory->CreateJSPromiseReactionsFunction(
MethodIndex::BUILTINS_PROMISE_HANDLER_RESOLVE);
resolve->SetPromise(thread, promise);
resolve->SetAlreadyResolved(thread, record);
JSHandle<JSPromiseReactionsFunction> reject = factory->CreateJSPromiseReactionsFunction(
MethodIndex::BUILTINS_PROMISE_HANDLER_REJECT);
reject->SetPromise(thread, promise);
reject->SetAlreadyResolved(thread, record);
JSHandle<ResolvingFunctionsRecord> reactions = factory->NewResolvingFunctionsRecord();
reactions->SetResolveFunction(thread, resolve.GetTaggedValue());
reactions->SetRejectFunction(thread, reject.GetTaggedValue());
return reactions;
}
JSTaggedValue JSPromise::FulfillPromise(JSThread *thread, const JSHandle<JSPromise> &promise,
const JSHandle<JSTaggedValue> &value)
{
if (thread->GetEcmaVM()->GetJSOptions().EnablePendingCheak()) {
thread->GetEcmaVM()->RemoveAsyncStackTrace(promise);
}
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
ASSERT_PRINT(promise->GetPromiseState() == PromiseState::PENDING, "FulfillPromise: state must be pending");
JSHandle<TaggedQueue> reactions(thread, promise->GetPromiseFulfillReactions(thread));
promise->SetPromiseResult(thread, value);
promise->SetPromiseFulfillReactions<SKIP_BARRIER>(thread, JSTaggedValue::Undefined());
promise->SetPromiseRejectReactions<SKIP_BARRIER>(thread, JSTaggedValue::Undefined());
promise->SetPromiseState(PromiseState::FULFILLED);
return TriggerPromiseReactions(thread, reactions, value);
}
JSHandle<PromiseCapability> JSPromise::NewPromiseCapability(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
if (!obj->IsConstructor()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: obj is not constructor!",
factory->NewPromiseCapability());
}
JSHandle<PromiseCapability> promiseCapability = factory->NewPromiseCapability();
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
if (obj == env->GetPromiseFunction()) {
JSHandle<JSPromise> promise = factory->NewJSPromise();
JSHandle<ResolvingFunctionsRecord> resolvingFunctions = JSPromise::CreateResolvingFunctions(thread, promise);
promiseCapability->SetPromise(thread, promise);
auto resolveFunc = resolvingFunctions->GetResolveFunction(thread);
auto rejectFunc = resolvingFunctions->GetRejectFunction(thread);
promiseCapability->SetResolve(thread, resolveFunc);
promiseCapability->SetReject(thread, rejectFunc);
return promiseCapability;
}
JSHandle<JSPromiseExecutorFunction> executor = factory->CreateJSPromiseExecutorFunction();
executor->SetCapability(thread, promiseCapability.GetTaggedValue());
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, obj, undefined, undefined, 1);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, factory->NewPromiseCapability());
info->SetCallArg(executor.GetTaggedValue());
JSTaggedValue result = JSFunction::Construct(info);
JSHandle<JSPromise> promise(thread, result);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, factory->NewPromiseCapability());
if (!promiseCapability->GetResolve(thread).IsCallable()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: resolve is not a callable function!",
factory->NewPromiseCapability());
}
if (!promiseCapability->GetReject(thread).IsCallable()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "NewPromiseCapability: reject is not a callable function!",
factory->NewPromiseCapability());
}
promiseCapability->SetPromise(thread, promise);
return promiseCapability;
}
bool JSPromise::IsPromise(const JSHandle<JSTaggedValue> &value)
{
if (!value->IsECMAObject()) {
return false;
}
if (!value->IsJSPromise()) {
return false;
}
return true;
}
JSTaggedValue JSPromise::RejectPromise(JSThread *thread, const JSHandle<JSPromise> &promise,
const JSHandle<JSTaggedValue> &reason)
{
if (thread->GetEcmaVM()->GetJSOptions().EnablePendingCheak()) {
thread->GetEcmaVM()->RemoveAsyncStackTrace(promise);
}
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
ASSERT_PRINT(promise->GetPromiseState() == PromiseState::PENDING, "RejectPromise: state must be pending");
JSHandle<TaggedQueue> reactions(thread,
TaggedQueue::Cast(promise->GetPromiseRejectReactions(thread).GetTaggedObject()));
promise->SetPromiseResult(thread, reason);
promise->SetPromiseFulfillReactions<SKIP_BARRIER>(thread, JSTaggedValue::Undefined());
promise->SetPromiseRejectReactions<SKIP_BARRIER>(thread, JSTaggedValue::Undefined());
promise->SetPromiseState(PromiseState::REJECTED);
if (!promise->GetPromiseIsHandled()) {
thread->GetEcmaVM()->PromiseRejectionTracker(promise, reason, PromiseRejectionEvent::REJECT);
}
return TriggerPromiseReactions(thread, reactions, reason);
}
JSTaggedValue JSPromise::TriggerPromiseReactions(JSThread *thread, const JSHandle<TaggedQueue> &reactions,
const JSHandle<JSTaggedValue> &argument)
{
JSHandle<job::MicroJobQueue> job = thread->GetEcmaVM()->GetMicroJobQueue();
JSHandle<GlobalEnv> globalEnv = thread->GetEcmaVM()->GetGlobalEnv();
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSFunction> promiseReactionsJob(globalEnv->GetPromiseReactionJob());
JSMutableHandle<PromiseReaction> reaction(thread, JSTaggedValue::Undefined());
while (!reactions->Empty(thread)) {
reaction.Update(reactions->Pop(thread));
JSHandle<TaggedArray> arguments = factory->NewTaggedArray(2);
arguments->Set(thread, 0, reaction);
arguments->Set(thread, 1, argument);
job::MicroJobQueue::EnqueueJob(thread, job, job::QueueType::QUEUE_PROMISE, promiseReactionsJob, arguments);
}
return globalConst->GetUndefined();
}
JSHandle<JSTaggedValue> JSPromise::IfThrowGetThrowValue(JSThread *thread)
{
return JSHandle<JSTaggedValue>(thread, thread->GetException());
}
}