Skip to content

Commit

Permalink
Promise.prototype.finally
Browse files Browse the repository at this point in the history
  • Loading branch information
rhuanjl committed Feb 12, 2018
1 parent 5384ef4 commit c8a61ee
Show file tree
Hide file tree
Showing 19 changed files with 2,469 additions and 2,074 deletions.
1 change: 1 addition & 0 deletions lib/Runtime/Base/JnDirectFields.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ ENTRY2(false_, _u("false")) // "false" cannot be an identifier in C++ so using "
ENTRY(flags)
ENTRY(fill)
ENTRY(filter)
ENTRY(finally)
ENTRY(find)
ENTRY(findIndex)
ENTRY(fixed)
Expand Down
934 changes: 467 additions & 467 deletions lib/Runtime/Library/InJavascript/Intl.js.bc.32b.h

Large diffs are not rendered by default.

938 changes: 469 additions & 469 deletions lib/Runtime/Library/InJavascript/Intl.js.bc.64b.h

Large diffs are not rendered by default.

928 changes: 464 additions & 464 deletions lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.32b.h

Large diffs are not rendered by default.

924 changes: 462 additions & 462 deletions lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.64b.h

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/Runtime/Library/JavascriptBuiltInFunctionList.h
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,7 @@ BUILTIN(JavascriptPromise, Race, EntryRace, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptPromise, Reject, EntryReject, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptPromise, Resolve, EntryResolve, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptPromise, Then, EntryThen, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptPromise, Finally, EntryFinally, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptPromise, Identity, EntryIdentityFunction, FunctionInfo::ErrorOnNew | FunctionInfo::DoNotProfile)
BUILTIN(JavascriptPromise, Thrower, EntryThrowerFunction, FunctionInfo::ErrorOnNew | FunctionInfo::DoNotProfile)
BUILTIN(JavascriptPromise, ResolveOrRejectFunction, EntryResolveOrRejectFunction, FunctionInfo::ErrorOnNew | FunctionInfo::DoNotProfile)
Expand Down
27 changes: 27 additions & 0 deletions lib/Runtime/Library/JavascriptLibrary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2446,6 +2446,10 @@ namespace Js
}
scriptContext->SetBuiltInLibraryFunction(JavascriptPromise::EntryInfo::Catch.GetOriginalEntryPoint(),
library->AddFunctionToLibraryObject(promisePrototype, PropertyIds::catch_, &JavascriptPromise::EntryInfo::Catch, 1));

scriptContext->SetBuiltInLibraryFunction(JavascriptPromise::EntryInfo::Finally.GetOriginalEntryPoint(),
library->AddFunctionToLibraryObject(promisePrototype, PropertyIds::finally, &JavascriptPromise::EntryInfo::Finally, 1));

library->AddMember(promisePrototype, PropertyIds::then, library->EnsurePromiseThenFunction(), PropertyBuiltInMethodDefaults);
scriptContext->SetBuiltInLibraryFunction(JavascriptPromise::EntryInfo::Then.GetOriginalEntryPoint(), library->EnsurePromiseThenFunction());

Expand Down Expand Up @@ -6929,6 +6933,29 @@ namespace Js
return function;
}

JavascriptPromiseThenFinallyFunction* JavascriptLibrary::CreatePromiseThenFinallyFunction(JavascriptMethod entryPoint, RecyclableObject* OnFinally, RecyclableObject* Constructor, bool shouldThrow)
{
Assert(scriptContext->GetConfig()->IsES6PromiseEnabled());

FunctionInfo* functionInfo = RecyclerNew(this->GetRecycler(), FunctionInfo, entryPoint);
DynamicType* type = DynamicType::New(scriptContext, TypeIds_Function, functionPrototype, entryPoint, GetDeferredAnonymousFunctionTypeHandler());

JavascriptPromiseThenFinallyFunction* function = RecyclerNewEnumClass(this->GetRecycler(), EnumFunctionClass, JavascriptPromiseThenFinallyFunction, type, functionInfo, OnFinally, Constructor, shouldThrow);
function->SetPropertyWithAttributes(PropertyIds::length, TaggedInt::ToVarUnchecked(1), PropertyConfigurable, nullptr);

return function;
}

JavascriptPromiseThunkFinallyFunction* JavascriptLibrary::CreatePromiseThunkFinallyFunction(JavascriptMethod entryPoint, Var value, bool shouldThrow)
{
Assert(scriptContext->GetConfig()->IsES6PromiseEnabled());

FunctionInfo* functionInfo = RecyclerNew(this->GetRecycler(), FunctionInfo, entryPoint);
DynamicType* type = CreateDeferredPrototypeFunctionType(entryPoint);

return RecyclerNewEnumClass(this->GetRecycler(), EnumFunctionClass, JavascriptPromiseThunkFinallyFunction, type, functionInfo, value, shouldThrow);
}

JavascriptExternalFunction* JavascriptLibrary::CreateWrappedExternalFunction(JavascriptExternalFunction* wrappedFunction)
{
// The wrapped function will have profiling, so the wrapper function does not need it.
Expand Down
2 changes: 2 additions & 0 deletions lib/Runtime/Library/JavascriptLibrary.h
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,8 @@ namespace Js
JavascriptPromiseReactionTaskFunction* CreatePromiseReactionTaskFunction(JavascriptMethod entryPoint, JavascriptPromiseReaction* reaction, Var argument);
JavascriptPromiseResolveThenableTaskFunction* CreatePromiseResolveThenableTaskFunction(JavascriptMethod entryPoint, JavascriptPromise* promise, RecyclableObject* thenable, RecyclableObject* thenFunction);
JavascriptPromiseAllResolveElementFunction* CreatePromiseAllResolveElementFunction(JavascriptMethod entryPoint, uint32 index, JavascriptArray* values, JavascriptPromiseCapability* capabilities, JavascriptPromiseAllResolveElementFunctionRemainingElementsWrapper* remainingElements);
JavascriptPromiseThenFinallyFunction* CreatePromiseThenFinallyFunction(JavascriptMethod entryPoint, RecyclableObject* OnFinally, RecyclableObject* Constructor, bool shouldThrow);
JavascriptPromiseThunkFinallyFunction* CreatePromiseThunkFinallyFunction(JavascriptMethod entryPoint, Var value, bool shouldThrow);
JavascriptExternalFunction* CreateWrappedExternalFunction(JavascriptExternalFunction* wrappedFunction);

#if ENABLE_NATIVE_CODEGEN
Expand Down
157 changes: 155 additions & 2 deletions lib/Runtime/Library/JavascriptPromise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,159 @@ namespace Js
return CreateThenPromise(promise, fulfillmentHandler, rejectionHandler, scriptContext);
}

// Promise.prototype.finally as described in the draft of ES 2018 Section 25.4.5.3
Var JavascriptPromise::EntryFinally(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
Assert(!(callInfo.Flags & CallFlags_New));

ScriptContext* scriptContext = function->GetScriptContext();

AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Promise.prototype.finally"));
//1. Let promise be the this value
//2. If Type(promise) is not Object, throw a TypeError exception
if (args.Info.Count < 1 || !JavascriptOperators::IsObject(args[0]))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedObject, _u("Promise.prototype.finally"));
}

JavascriptLibrary* library = scriptContext->GetLibrary();
RecyclableObject* promise = RecyclableObject::UnsafeFromVar(args[0]);
//3. Let C be ? SpeciesConstructor(promise, %Promise%).
RecyclableObject* constructor = JavascriptOperators::SpeciesConstructor(promise, scriptContext->GetLibrary()->GetPromiseConstructor(), scriptContext);
//4. Assert IsConstructor(C)
Assert(JavascriptOperators::IsConstructor(constructor));

//5. If IsCallable(onFinally) is false
// a. Let thenFinally be onFinally
// b. Let catchFinally be onFinally
//6. Else,
// a. Let thenFinally be a new built-in function object as defined in ThenFinally Function.
// b. Let catchFinally be a new built-in function object as defined in CatchFinally Function.
// c. Set thenFinally and catchFinally's [[Constructor]] internal slots to C.
// d. Set thenFinally and catchFinally's [[OnFinally]] internal slots to onFinally.

Var thenFinally = nullptr;
Var catchFinally = nullptr;

if (args.Info.Count > 1)
{
if (JavascriptConversion::IsCallable(args[1]))
{
//note to avoid duplicating code the ThenFinallyFunction works as both thenFinally and catchFinally using a flag
thenFinally = library->CreatePromiseThenFinallyFunction(EntryThenFinallyFunction, RecyclableObject::FromVar(args[1]), constructor, false);
catchFinally = library->CreatePromiseThenFinallyFunction(EntryThenFinallyFunction, RecyclableObject::FromVar(args[1]), constructor, true);
}
else
{
thenFinally = args[1];
catchFinally = args[1];
}
}
else
{
thenFinally = library->GetUndefined();
catchFinally = library->GetUndefined();
}

Assert(thenFinally != nullptr && catchFinally != nullptr);

//7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
Var funcVar = JavascriptOperators::GetProperty(promise, Js::PropertyIds::then, scriptContext);
if (!JavascriptConversion::IsCallable(funcVar))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Promise.prototype.finally"));
}
RecyclableObject* func = RecyclableObject::UnsafeFromVar(funcVar);

return CALL_FUNCTION(scriptContext->GetThreadContext(),
func, Js::CallInfo(CallFlags_Value, 3),
promise,
thenFinally,
catchFinally);
}

//ThenFinallyFunction as described in draft of ES2018 section 25.4.5.3.1
//AND CatchFinallyFunction as described in draft of ES2018 section 25.4.5.3.2
Var JavascriptPromise::EntryThenFinallyFunction(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
Assert(!(callInfo.Flags & CallFlags_New));
ScriptContext* scriptContext = function->GetScriptContext();

JavascriptLibrary* library = scriptContext->GetLibrary();

JavascriptPromiseThenFinallyFunction* thenFinallyFunction = JavascriptPromiseThenFinallyFunction::FromVar(function);

//1. Let onFinally be F.[[OnFinally]]
//2. Assert: IsCallabale(onFinally)=true
Assert(JavascriptConversion::IsCallable(thenFinallyFunction->GetOnFinally()));

//3. Let result be ? Call(onFinally, undefined)
Var result = CALL_FUNCTION(scriptContext->GetThreadContext(), thenFinallyFunction->GetOnFinally(), CallInfo(CallFlags_Value, 1), library->GetUndefined());

//4. Let C be F.[[Constructor]]
//5. Assert IsConstructor(C)
Assert(JavascriptOperators::IsConstructor(thenFinallyFunction->GetConstructor()));

//6. Let promise be ? PromiseResolve(c, result)
Var promiseVar = CreateResolvedPromise(result, scriptContext, thenFinallyFunction->GetConstructor());

//7. Let valueThunk be equivalent to a function that returns value
//OR 7. Let thrower be equivalent to a function that throws reason

Var valueOrReason = nullptr;

if(args.Info.Count > 1)
{
valueOrReason = args[1];
}
else
{
valueOrReason = scriptContext->GetLibrary()->GetUndefined();
}

JavascriptPromiseThunkFinallyFunction* thunkFinallyFunction = library->CreatePromiseThunkFinallyFunction(EntryThunkFinallyFunction, valueOrReason, thenFinallyFunction->GetShouldThrow());

//8. Return ? Invoke(promise, "then", <<valueThunk>>)
RecyclableObject* promise = JavascriptOperators::ToObject(promiseVar, scriptContext);
Var funcVar = JavascriptOperators::GetProperty(promise, Js::PropertyIds::then, scriptContext);

if (!JavascriptConversion::IsCallable(funcVar))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Promise.prototype.finally"));
}

RecyclableObject* func = RecyclableObject::FromVar(funcVar);

return CALL_FUNCTION(scriptContext->GetThreadContext(),
func, Js::CallInfo(CallFlags_Value, 2),
promiseVar,
thunkFinallyFunction);
}

// valueThunk Function as described in draft ES 2018Section 25.4.5.3.1.7
// and thrower as described in draft ES 2018Section 25.4.5.3.2.7
Var JavascriptPromise::EntryThunkFinallyFunction(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
Assert(!(callInfo.Flags & CallFlags_New));

JavascriptPromiseThunkFinallyFunction* thunkFinallyFunction = JavascriptPromiseThunkFinallyFunction::FromVar(function);

if (!thunkFinallyFunction->GetShouldThrow())
{
return thunkFinallyFunction->GetValue();
}
else
{
JavascriptExceptionOperators::Throw(thunkFinallyFunction->GetValue(), function->GetScriptContext());
}
}

// Promise Reject and Resolve Functions as described in ES 2015 Section 25.4.1.4.1 and 25.4.1.4.2
Var JavascriptPromise::EntryResolveOrRejectFunction(RecyclableObject* function, CallInfo callInfo, ...)
{
Expand Down Expand Up @@ -902,8 +1055,8 @@ namespace Js
{
Assert(args[1] != nullptr);

return args[1];
}
return args[1];
}
else
{
return function->GetScriptContext()->GetLibrary()->GetUndefined();
Expand Down
85 changes: 85 additions & 0 deletions lib/Runtime/Library/JavascriptPromise.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,85 @@ namespace Js
#endif
};

class JavascriptPromiseThenFinallyFunction : public RuntimeFunction
{
protected:
DEFINE_VTABLE_CTOR(JavascriptPromiseThenFinallyFunction, RuntimeFunction);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptPromiseThenFinallyFunction);

public:
JavascriptPromiseThenFinallyFunction(DynamicType* type, FunctionInfo* functionInfo, RecyclableObject* OnFinally, RecyclableObject* Constructor, bool shouldThrow)
: RuntimeFunction(type, functionInfo), OnFinally(OnFinally), Constructor(Constructor), shouldThrow(shouldThrow)
{ }

inline static bool Is(Var var)
{
if (JavascriptFunction::Is(var))
{
JavascriptFunction* obj = JavascriptFunction::UnsafeFromVar(var);

return VirtualTableInfo<JavascriptPromiseThenFinallyFunction>::HasVirtualTable(obj)
|| VirtualTableInfo<CrossSiteObject<JavascriptPromiseThenFinallyFunction>>::HasVirtualTable(obj);
}

return false;
}

inline static JavascriptPromiseThenFinallyFunction* FromVar(Var var)
{
AssertOrFailFast(JavascriptPromiseThenFinallyFunction::Is(var));

return static_cast<JavascriptPromiseThenFinallyFunction*>(var);
}

inline bool GetShouldThrow() { return this->shouldThrow; }
inline RecyclableObject* GetOnFinally() { return this->OnFinally; }
inline RecyclableObject* GetConstructor() { return this->Constructor; }

private:
Field(RecyclableObject*) OnFinally;
Field(RecyclableObject*) Constructor;
Field(bool) shouldThrow;
};

class JavascriptPromiseThunkFinallyFunction : public RuntimeFunction
{
protected:
DEFINE_VTABLE_CTOR(JavascriptPromiseThunkFinallyFunction, RuntimeFunction);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptPromiseThunkFinallyFunction);

public:
JavascriptPromiseThunkFinallyFunction(DynamicType* type, FunctionInfo* functionInfo, Var value, bool shouldThrow)
: RuntimeFunction(type, functionInfo), value(value), shouldThrow(shouldThrow)
{ }

inline static bool Is(Var var)
{
if (JavascriptFunction::Is(var))
{
JavascriptFunction* obj = JavascriptFunction::UnsafeFromVar(var);

return VirtualTableInfo<JavascriptPromiseThunkFinallyFunction>::HasVirtualTable(obj)
|| VirtualTableInfo<CrossSiteObject<JavascriptPromiseThunkFinallyFunction>>::HasVirtualTable(obj);
}
return false;
}

inline static JavascriptPromiseThunkFinallyFunction* FromVar(Var var)
{
AssertOrFailFast(JavascriptPromiseThunkFinallyFunction::Is(var));

return static_cast<JavascriptPromiseThunkFinallyFunction*>(var);
}

inline bool GetShouldThrow() { return this->shouldThrow; }
inline Var GetValue() { return this->value; }

private:
Field(Var) value;
Field(bool) shouldThrow;
};

struct JavascriptPromiseAllResolveElementFunctionRemainingElementsWrapper
{
Field(uint32) remainingElements;
Expand Down Expand Up @@ -388,10 +467,13 @@ namespace Js
static FunctionInfo Reject;
static FunctionInfo Resolve;
static FunctionInfo Then;
static FunctionInfo Finally;

static FunctionInfo Identity;
static FunctionInfo Thrower;

static FunctionInfo FinallyValueFunction;
static FunctionInfo ThenFinallyFunction;
static FunctionInfo ResolveOrRejectFunction;
static FunctionInfo CapabilitiesExecutorFunction;
static FunctionInfo AllResolveElementFunction;
Expand All @@ -409,7 +491,10 @@ namespace Js
static Var EntryReject(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryResolve(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryThen(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryFinally(RecyclableObject* function, CallInfo callInfo, ...);

static Var EntryThunkFinallyFunction(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryThenFinallyFunction(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryCapabilitiesExecutorFunction(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryResolveOrRejectFunction(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryReactionTaskFunction(RecyclableObject* function, CallInfo callInfo, ...);
Expand Down
Loading

0 comments on commit c8a61ee

Please sign in to comment.