diff --git a/bin/ChakraCore/ChakraCore.def b/bin/ChakraCore/ChakraCore.def index 38df743e296..3010e888d9b 100644 --- a/bin/ChakraCore/ChakraCore.def +++ b/bin/ChakraCore/ChakraCore.def @@ -62,3 +62,5 @@ JsLessThan JsLessThanOrEqual JsCreateEnhancedFunction + +JsSetHostPromiseRejectionTracker diff --git a/bin/ch/ChakraRtInterface.cpp b/bin/ch/ChakraRtInterface.cpp index 191fa1c499f..503d6b8c025 100644 --- a/bin/ch/ChakraRtInterface.cpp +++ b/bin/ch/ChakraRtInterface.cpp @@ -120,6 +120,7 @@ bool ChakraRTInterface::LoadChakraDll(ArgInfo* argInfo, HINSTANCE *outLibrary) m_jsApiHooks.pfJsrtGetValueType = (JsAPIHooks::JsrtGetValueType)GetChakraCoreSymbol(library, "JsGetValueType"); m_jsApiHooks.pfJsrtSetIndexedProperty = (JsAPIHooks::JsrtSetIndexedPropertyPtr)GetChakraCoreSymbol(library, "JsSetIndexedProperty"); m_jsApiHooks.pfJsrtSetPromiseContinuationCallback = (JsAPIHooks::JsrtSetPromiseContinuationCallbackPtr)GetChakraCoreSymbol(library, "JsSetPromiseContinuationCallback"); + m_jsApiHooks.pfJsrtSetHostPromiseRejectionTracker = (JsAPIHooks::JsrtSetHostPromiseRejectionTrackerPtr)GetChakraCoreSymbol(library, "JsSetHostPromiseRejectionTracker"); m_jsApiHooks.pfJsrtGetContextOfObject = (JsAPIHooks::JsrtGetContextOfObject)GetChakraCoreSymbol(library, "JsGetContextOfObject"); m_jsApiHooks.pfJsrtInitializeModuleRecord = (JsAPIHooks::JsInitializeModuleRecordPtr)GetChakraCoreSymbol(library, "JsInitializeModuleRecord"); m_jsApiHooks.pfJsrtParseModuleSource = (JsAPIHooks::JsParseModuleSourcePtr)GetChakraCoreSymbol(library, "JsParseModuleSource"); diff --git a/bin/ch/ChakraRtInterface.h b/bin/ch/ChakraRtInterface.h index a81cd63d397..5bd00024930 100644 --- a/bin/ch/ChakraRtInterface.h +++ b/bin/ch/ChakraRtInterface.h @@ -54,6 +54,7 @@ struct JsAPIHooks typedef JsErrorCode (WINAPI *JsrtGetValueType)(JsValueRef value, JsValueType *type); typedef JsErrorCode (WINAPI *JsrtSetIndexedPropertyPtr)(JsValueRef object, JsValueRef index, JsValueRef value); typedef JsErrorCode (WINAPI *JsrtSetPromiseContinuationCallbackPtr)(JsPromiseContinuationCallback callback, void *callbackState); + typedef JsErrorCode (WINAPI *JsrtSetHostPromiseRejectionTrackerPtr)(JsHostPromiseRejectionTrackerCallback callback, void *callbackState); typedef JsErrorCode (WINAPI *JsrtGetContextOfObject)(JsValueRef object, JsContextRef *callbackState); typedef JsErrorCode(WINAPI *JsrtDiagStartDebugging)(JsRuntimeHandle runtimeHandle, JsDiagDebugEventCallback debugEventCallback, void* callbackState); @@ -152,6 +153,7 @@ struct JsAPIHooks JsrtGetValueType pfJsrtGetValueType; JsrtSetIndexedPropertyPtr pfJsrtSetIndexedProperty; JsrtSetPromiseContinuationCallbackPtr pfJsrtSetPromiseContinuationCallback; + JsrtSetHostPromiseRejectionTrackerPtr pfJsrtSetHostPromiseRejectionTracker; JsrtGetContextOfObject pfJsrtGetContextOfObject; JsrtDiagStartDebugging pfJsrtDiagStartDebugging; JsrtDiagStopDebugging pfJsrtDiagStopDebugging; @@ -356,6 +358,7 @@ class ChakraRTInterface static JsErrorCode WINAPI JsGetValueType(JsValueRef value, JsValueType *type) { return HOOK_JS_API(GetValueType(value, type)); } static JsErrorCode WINAPI JsSetIndexedProperty(JsValueRef object, JsValueRef index, JsValueRef value) { return HOOK_JS_API(SetIndexedProperty(object, index, value)); } static JsErrorCode WINAPI JsSetPromiseContinuationCallback(JsPromiseContinuationCallback callback, void *callbackState) { return HOOK_JS_API(SetPromiseContinuationCallback(callback, callbackState)); } + static JsErrorCode WINAPI JsSetHostPromiseRejectionTracker(JsHostPromiseRejectionTrackerCallback callback, void *callbackState) { return HOOK_JS_API(SetHostPromiseRejectionTracker(callback, callbackState)); } static JsErrorCode WINAPI JsGetContextOfObject(JsValueRef object, JsContextRef* context) { return HOOK_JS_API(GetContextOfObject(object, context)); } static JsErrorCode WINAPI JsDiagStartDebugging(JsRuntimeHandle runtimeHandle, JsDiagDebugEventCallback debugEventCallback, void* callbackState) { return HOOK_JS_API(DiagStartDebugging(runtimeHandle, debugEventCallback, callbackState)); } static JsErrorCode WINAPI JsDiagStopDebugging(JsRuntimeHandle runtimeHandle, void** callbackState) { return HOOK_JS_API(DiagStopDebugging(runtimeHandle, callbackState)); } diff --git a/bin/ch/HostConfigFlagsList.h b/bin/ch/HostConfigFlagsList.h index a2fa5bd75b1..0749efa34ec 100644 --- a/bin/ch/HostConfigFlagsList.h +++ b/bin/ch/HostConfigFlagsList.h @@ -15,5 +15,6 @@ FLAG(bool, IgnoreScriptErrorCode, "Don't return error code on script e FLAG(bool, MuteHostErrorMsg, "Mute host error output, e.g. module load failures", false) FLAG(bool, TraceHostCallback, "Output traces for host callbacks", false) FLAG(bool, Test262, "load Test262 harness", false) +FLAG(bool, TrackRejectedPromises, "Enable tracking of unhandled promise rejections", false) #undef FLAG #endif diff --git a/bin/ch/WScriptJsrt.cpp b/bin/ch/WScriptJsrt.cpp index 485ed60d73c..10e9b464d81 100644 --- a/bin/ch/WScriptJsrt.cpp +++ b/bin/ch/WScriptJsrt.cpp @@ -1871,3 +1871,32 @@ void WScriptJsrt::PromiseContinuationCallback(JsValueRef task, void *callbackSta WScriptJsrt::CallbackMessage *msg = new WScriptJsrt::CallbackMessage(0, task); messageQueue->InsertSorted(msg); } + +void WScriptJsrt::PromiseRejectionTrackerCallback(JsValueRef promise, JsValueRef reason, bool handled, void *callbackState) +{ + Assert(promise != JS_INVALID_REFERENCE); + Assert(reason != JS_INVALID_REFERENCE); + JsValueRef strValue; + JsErrorCode error = ChakraRTInterface::JsConvertValueToString(reason, &strValue); + + if(!handled) + { + wprintf(_u("Uncaught promise rejection\n")); + } + else + { + wprintf(_u("Promise rejection handled\n")); + } + + if (error == JsNoError) + { + AutoString str(strValue); + if (str.GetError() == JsNoError) + { + wprintf(_u("%ls\n"), str.GetWideString()); + } + } + + fflush(stdout); +} + diff --git a/bin/ch/WScriptJsrt.h b/bin/ch/WScriptJsrt.h index 3ac3be5452f..580baaca933 100644 --- a/bin/ch/WScriptJsrt.h +++ b/bin/ch/WScriptJsrt.h @@ -58,6 +58,7 @@ class WScriptJsrt static JsErrorCode NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar); static JsErrorCode InitializeModuleCallbacks(); static void CALLBACK PromiseContinuationCallback(JsValueRef task, void *callbackState); + static void CALLBACK PromiseRejectionTrackerCallback(JsValueRef promise, JsValueRef reason, bool handled, void *callbackState); static LPCWSTR ConvertErrorCodeToMessage(JsErrorCode errorCode) { diff --git a/bin/ch/ch.cpp b/bin/ch/ch.cpp index fbd39ecfe47..ac3a3ad66fc 100644 --- a/bin/ch/ch.cpp +++ b/bin/ch/ch.cpp @@ -757,6 +757,11 @@ HRESULT ExecuteTest(const char* fileName) IfFailGo(E_FAIL); } + if(HostConfigFlags::flags.TrackRejectedPromises) + { + ChakraRTInterface::JsSetHostPromiseRejectionTracker(WScriptJsrt::PromiseRejectionTrackerCallback, nullptr); + } + len = strlen(fullPath); if (HostConfigFlags::flags.GenerateLibraryByteCodeHeaderIsEnabled) { diff --git a/lib/Jsrt/ChakraCore.h b/lib/Jsrt/ChakraCore.h index c0c7dd2a1cb..2aefcfd9aff 100644 --- a/lib/Jsrt/ChakraCore.h +++ b/lib/Jsrt/ChakraCore.h @@ -130,6 +130,27 @@ typedef struct JsNativeFunctionInfo /// The result of the call, if any. typedef _Ret_maybenull_ JsValueRef(CHAKRA_CALLBACK * JsEnhancedNativeFunction)(_In_ JsValueRef callee, _In_ JsValueRef *arguments, _In_ unsigned short argumentCount, _In_ JsNativeFunctionInfo *info, _In_opt_ void *callbackState); +/// +/// A Promise Rejection Tracker callback. +/// +/// +/// The host can specify a promise rejection tracker callback in JsSetHostPromiseRejectionTracker. +/// If a promise is rejected with no reactions or a reaction is added to a promise that was rejected +/// before it had reactions by default nothing is done. +/// A Promise Rejection Tracker callback may be set - which will then be called when this occurs. +/// Note - per draft ECMASpec 2018 25.4.1.9 this function should not set or return an exception +/// Note also the promise and reason parameters may be garbage collected after this function is called +/// if you wish to make further use of them you will need to use JsAddRef to preserve them +/// However if you use JsAddRef you must also call JsRelease and not hold unto them after +/// a handled notification (both per spec and to avoid memory leaks) +/// +/// The promise object, represented as a JsValueRef. +/// The value/cause of the rejection, represented as a JsValueRef. +/// Boolean - false for promiseRejected: i.e. if the promise has just been rejected with no handler, +/// true for promiseHandled: i.e. if it was rejected before without a handler and is now being handled. +/// The state passed to JsSetHostPromiseRejectionTracker. +typedef void (CHAKRA_CALLBACK *JsHostPromiseRejectionTrackerCallback)(_In_ JsValueRef promise, _In_ JsValueRef reason, _In_ bool handled, _In_opt_ void *callbackState); + /// /// Creates a new enhanced JavaScript function. /// @@ -993,5 +1014,27 @@ CHAKRA_API _In_ JsValueRef object, _In_ JsValueRef key, _Out_ bool *hasOwnProperty); + +/// +/// Sets whether any action should be taken when a promise is rejected with no reactions +/// or a reaction is added to a promise that was rejected before it had reactions. +/// By default in either of these cases nothing occurs. +/// This function allows you to specify if something should occur and provide a callback +/// to implement whatever should occur. +/// +/// +/// Requires an active script context. +/// +/// The callback function being set. +/// +/// User provided state that will be passed back to the callback. +/// +/// +/// The code JsNoError if the operation succeeded, a failure code otherwise. +/// +CHAKRA_API + JsSetHostPromiseRejectionTracker( + _In_ JsHostPromiseRejectionTrackerCallback promiseRejectionTrackerCallback, + _In_opt_ void *callbackState); #endif // _CHAKRACOREBUILD #endif // _CHAKRACORE_H_ diff --git a/lib/Jsrt/Jsrt.cpp b/lib/Jsrt/Jsrt.cpp index 2157ba062db..994d2f59cd7 100644 --- a/lib/Jsrt/Jsrt.cpp +++ b/lib/Jsrt/Jsrt.cpp @@ -5334,4 +5334,13 @@ CHAKRA_API JsGetDataViewInfo( END_JSRT_NO_EXCEPTION } +CHAKRA_API JsSetHostPromiseRejectionTracker(_In_ JsHostPromiseRejectionTrackerCallback promiseRejectionTrackerCallback, _In_opt_ void *callbackState) +{ + return ContextAPINoScriptWrapper_NoRecord([&](Js::ScriptContext *scriptContext) -> JsErrorCode { + scriptContext->GetLibrary()->SetNativeHostPromiseRejectionTrackerCallback((Js::JavascriptLibrary::HostPromiseRejectionTrackerCallback) promiseRejectionTrackerCallback, callbackState); + return JsNoError; + }, + /*allowInObjectBeforeCollectCallback*/true); +} + #endif // _CHAKRACOREBUILD diff --git a/lib/Runtime/Library/JavascriptLibrary.cpp b/lib/Runtime/Library/JavascriptLibrary.cpp index e7b91ff2aff..213836ea568 100644 --- a/lib/Runtime/Library/JavascriptLibrary.cpp +++ b/lib/Runtime/Library/JavascriptLibrary.cpp @@ -5295,6 +5295,32 @@ namespace Js this->nativeHostPromiseContinuationFunctionState = state; } + void JavascriptLibrary::SetNativeHostPromiseRejectionTrackerCallback(HostPromiseRejectionTrackerCallback function, void *state) + { + this->nativeHostPromiseRejectionTracker = function; + this->nativeHostPromiseContinuationFunctionState = state; + } + + void JavascriptLibrary::CallNativeHostPromiseRejectionTracker(Var promise, Var reason, bool handled) + { + if(nativeHostPromiseRejectionTracker != nullptr) + { + BEGIN_LEAVE_SCRIPT(scriptContext); + try + { + nativeHostPromiseRejectionTracker(promise, reason, handled, this->nativeHostPromiseContinuationFunctionState); + } + catch (...) + { + // Hosts are required not to pass exceptions back across the callback boundary. If + // this happens, it is a bug in the host, not something that we are expected to + // handle gracefully. + Js::Throw::FatalInternalError(); + } + END_LEAVE_SCRIPT(scriptContext); + } + } + void JavascriptLibrary::SetJsrtContext(FinalizableObject* jsrtContext) { // With JsrtContext supporting cross context, ensure that it doesn't get GCed diff --git a/lib/Runtime/Library/JavascriptLibrary.h b/lib/Runtime/Library/JavascriptLibrary.h index 6d94aedaee6..2f2ecd6a256 100644 --- a/lib/Runtime/Library/JavascriptLibrary.h +++ b/lib/Runtime/Library/JavascriptLibrary.h @@ -242,6 +242,7 @@ namespace Js static DWORD GetRandSeed1Offset() { return offsetof(JavascriptLibrary, randSeed1); } static DWORD GetTypeDisplayStringsOffset() { return offsetof(JavascriptLibrary, typeDisplayStrings); } typedef bool (CALLBACK *PromiseContinuationCallback)(Var task, void *callbackState); + typedef void (CALLBACK *HostPromiseRejectionTrackerCallback)(Var promise, Var reason, bool handled, void *callbackState); Var GetUndeclBlockVar() const { return undeclBlockVarSentinel; } bool IsUndeclBlockVar(Var var) const { return var == undeclBlockVarSentinel; } @@ -492,6 +493,9 @@ namespace Js FieldNoBarrier(PromiseContinuationCallback) nativeHostPromiseContinuationFunction; Field(void *) nativeHostPromiseContinuationFunctionState; + FieldNoBarrier(HostPromiseRejectionTrackerCallback) nativeHostPromiseRejectionTracker = nullptr; + Field(void *) nativeHostPromiseRejectionTrackerState; + typedef SList FunctionReferenceList; typedef JsUtil::WeakReferenceDictionary> JsrtExternalTypesCache; @@ -949,6 +953,8 @@ namespace Js JavascriptFunction* GetThrowerFunction() const { return throwerFunction; } void SetNativeHostPromiseContinuationFunction(PromiseContinuationCallback function, void *state); + void SetNativeHostPromiseRejectionTrackerCallback(HostPromiseRejectionTrackerCallback function, void *state); + void CallNativeHostPromiseRejectionTracker(Var promise, Var reason, bool handled); void SetJsrtContext(FinalizableObject* jsrtContext); FinalizableObject* GetJsrtContext(); diff --git a/lib/Runtime/Library/JavascriptPromise.cpp b/lib/Runtime/Library/JavascriptPromise.cpp index 27a33e62bcf..07a1424d2c8 100644 --- a/lib/Runtime/Library/JavascriptPromise.cpp +++ b/lib/Runtime/Library/JavascriptPromise.cpp @@ -12,6 +12,7 @@ namespace Js Assert(type->GetTypeId() == TypeIds_Promise); this->status = PromiseStatusCode_Undefined; + this->isHandled = false; this->result = nullptr; this->resolveReactions = nullptr; this->rejectReactions = nullptr; @@ -660,6 +661,10 @@ namespace Js { reactions = this->GetRejectReactions(); newStatus = PromiseStatusCode_HasRejection; + if(!GetIsHandled()) + { + scriptContext->GetLibrary()->CallNativeHostPromiseRejectionTracker(this, resolution, false); + } } else { @@ -838,6 +843,10 @@ namespace Js EnqueuePromiseReactionTask(resolveReaction, sourcePromise->result, scriptContext); break; case PromiseStatusCode_HasRejection: + if(!sourcePromise->GetIsHandled()) + { + scriptContext->GetLibrary()->CallNativeHostPromiseRejectionTracker(sourcePromise, sourcePromise->result, true); + } EnqueuePromiseReactionTask(rejectReaction, sourcePromise->result, scriptContext); break; default: @@ -845,6 +854,8 @@ namespace Js break; } + sourcePromise->SetIsHandled(); + return promiseCapability->GetPromise(); } diff --git a/lib/Runtime/Library/JavascriptPromise.h b/lib/Runtime/Library/JavascriptPromise.h index ae3b6791eb8..dbf0a2683eb 100644 --- a/lib/Runtime/Library/JavascriptPromise.h +++ b/lib/Runtime/Library/JavascriptPromise.h @@ -461,6 +461,8 @@ namespace Js PromiseStatusCode_HasRejection }; + bool GetIsHandled() { return isHandled; } + void SetIsHandled() { isHandled = true; } PromiseStatus GetStatus() const { return status; } Var GetResult() const { return result; } @@ -470,6 +472,7 @@ namespace Js protected: Field(PromiseStatus) status; Field(Var) result; + Field(bool) isHandled; Field(JavascriptPromiseReactionList*) resolveReactions; Field(JavascriptPromiseReactionList*) rejectReactions; diff --git a/test/es7/PromiseRejectionTracking.baseline b/test/es7/PromiseRejectionTracking.baseline new file mode 100644 index 00000000000..b132c8fa267 --- /dev/null +++ b/test/es7/PromiseRejectionTracking.baseline @@ -0,0 +1,43 @@ +Executing test #1 - Reject promise with no reactions. +Uncaught promise rejection +Rejection from test 1 +Executing test #2 - Reject promise with a catch reaction only. +Executing test #3 - Reject promise with catch and then reactions. +Executing test #4 - Reject promise then add a catch afterwards. +Uncaught promise rejection +Rejection from test 4 +Promise rejection handled +Rejection from test 4 +Executing test #5 - Reject promise then add two catches afterwards. +Uncaught promise rejection +Rejection from test 5 +Promise rejection handled +Rejection from test 5 +Executing test #6 - Async function that throws. +Uncaught promise rejection +Rejection from test 6 +Executing test #7 - Async function that throws but is caught. +Uncaught promise rejection +Rejection from test 7 +Promise rejection handled +Rejection from test 7 +Executing test #8 - Async function that awaits a function that throws. +Uncaught promise rejection +Rejection from test 8 +Promise rejection handled +Rejection from test 8 +Executing test #9 - Reject a handled promise then handle one of the handles but not the other. +Executing test #10 - Reject a handled promise and don't handle either path. +Begin async results: +Uncaught promise rejection +Rejection from test 8 +Uncaught promise rejection +Rejection from test 9 +Uncaught promise rejection +Rejection from test 10 +Uncaught promise rejection +Rejection from test 10 +Promise rejection handled +Rejection from test 9 +Uncaught promise rejection +Rejection from test 9 diff --git a/test/es7/PromiseRejectionTracking.js b/test/es7/PromiseRejectionTracking.js new file mode 100644 index 00000000000..0b24fe906b1 --- /dev/null +++ b/test/es7/PromiseRejectionTracking.js @@ -0,0 +1,145 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// Test HostPromiseRejectionTracker - see ecma262 section 25.4.1.9 + +let tests = [ + { + name: "Reject promise with no reactions.", + body: function(index) + { + let controller; + let promise = new Promise((resolve, reject)=>{ + controller = {resolve, reject}; + }); + controller.reject("Rejection from test " + index);//Should notify rejected + } + }, + { + name: "Reject promise with a catch reaction only.", + body: function(index) + { + let controller; + let promise = new Promise((resolve, reject)=>{ + controller = {resolve, reject}; + }).catch(()=>{}); + controller.reject("Rejection from test " + index);//Should NOT notify + } + }, + { + name: "Reject promise with catch and then reactions.", + body: function(index) + { + let controller; + let promise = new Promise((resolve, reject)=>{ + controller = {resolve, reject}; + }).then(()=>{}).catch(()=>{}); + controller.reject("Rejection from test " + index);//Should NOT notify + } + }, + { + name: "Reject promise then add a catch afterwards.", + body: function(index) + { + let controller; + let promise = new Promise((resolve, reject)=>{ + controller = {resolve, reject}; + }); + controller.reject("Rejection from test " + index);//Should notify rejected + promise.catch(()=>{});//Should notify handled + } + }, + { + name: "Reject promise then add two catches afterwards.", + body: function(index) + { + let controller; + let promise = new Promise((resolve, reject)=>{ + controller = {resolve, reject}; + }); + controller.reject("Rejection from test " + index);//Should notify rejected + promise.catch(()=>{});//Should notify handled + promise.catch(()=>{});//Should NOT notify + } + }, + { + name: "Async function that throws.", + body: function(index) + { + async function aFunction() + { + throw ("Rejection from test " + index); + } + aFunction();//Should notify rejected + } + }, + { + name: "Async function that throws but is caught.", + body: function(index) + { + async function aFunction() + { + throw ("Rejection from test " + index); + } + aFunction().catch(()=>{});//Should notify rejected AND then handled + } + }, + { + name: "Async function that awaits a function that throws.", + body: function(index) + { + async function aFunction() + { + throw ("Rejection from test " + index);//Should notify rejected + } + async function bFunction() + { + await aFunction();//Should notify handled + } + bFunction();//Should notify rejected in the async section + }, + }, + { + name: "Reject a handled promise then handle one of the handles but not the other.", + body: function(index) + { + let controller; + let promise = new Promise((resolve, reject) => { controller = {resolve, reject};}); + let a = promise.then(() => {});//a is not handled + let b = promise.then(() => {});//b is not handled + controller.reject("Rejection from test " + index);//no notification as handled + + let c = a.then(() => {}); //handle a + + c.catch(() => {b.then(()=>{})}); // handle c + //b is still not handled -> notify once in async section + //b has an async handler -> will notify handled in async section + //the .then() on b is not handled so will notify in async section + }, + }, + { + name: "Reject a handled promise and don't handle either path.", + body: function(index) + { + let controller; + let promise = new Promise((resolve, reject) => { controller = {resolve, reject};}); + let a = promise.then(() => {});//a is not handled + let b = promise.then(() => {});//b is not handled + controller.reject("Rejection from test " + index);//no notification as handled + + let c = a.then(() => {}); //handle a + + //b is not handled -> will notify in async section + //c is not handled -> will notify in async section + } + } +]; + +for(let i = 0; i < tests.length; ++i) +{ + WScript.Echo('Executing test #' + (i + 1) + ' - ' + tests[i].name); + tests[i].body(i+1); +} +WScript.Echo("Begin async results:"); diff --git a/test/es7/rlexe.xml b/test/es7/rlexe.xml index 9a23c30fd1c..8c34409e4c2 100644 --- a/test/es7/rlexe.xml +++ b/test/es7/rlexe.xml @@ -92,4 +92,11 @@ exclude_xplat + + + PromiseRejectionTracking.js + -TrackRejectedPromises -args summary -endargs -nodeferparse + PromiseRejectionTracking.baseline + +