зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1568903 - Part 9: Implement the Promise.any proposal. r=jorendorff
As with AggregateError, `Promise.any` is only enabled in Nightly. Now that everything is in place, the actual `Promise.any` implementation is relatively straight forward. The only tricky part is probably just the `ThrowAggregateError` function, when the async stack is created to give a better stack trace. Differential Revision: https://phabricator.services.mozilla.com/D51659 --HG-- rename : js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js => js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js extra : moz-landing-system : lando
This commit is contained in:
Родитель
10045f84fe
Коммит
d75254445f
|
@ -20,8 +20,10 @@
|
|||
#include "js/ForOfIterator.h" // JS::ForOfIterator
|
||||
#include "js/PropertySpec.h"
|
||||
#include "util/Poison.h"
|
||||
#include "vm/ArrayObject.h"
|
||||
#include "vm/AsyncFunction.h"
|
||||
#include "vm/AsyncIteration.h"
|
||||
#include "vm/ErrorObject.h"
|
||||
#include "vm/GeneratorObject.h"
|
||||
#include "vm/Iteration.h"
|
||||
#include "vm/JSContext.h"
|
||||
|
@ -30,6 +32,7 @@
|
|||
|
||||
#include "debugger/DebugAPI-inl.h"
|
||||
#include "vm/Compartment-inl.h"
|
||||
#include "vm/ErrorObject-inl.h"
|
||||
#include "vm/JSObject-inl.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
|
@ -2389,22 +2392,27 @@ static MOZ_MUST_USE bool PerformPromiseAllSettled(
|
|||
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
|
||||
Handle<PromiseCapability> resultCapability, bool* done);
|
||||
|
||||
static MOZ_MUST_USE bool PerformPromiseAny(
|
||||
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
|
||||
Handle<PromiseCapability> resultCapability, bool* done);
|
||||
|
||||
static MOZ_MUST_USE bool PerformPromiseRace(
|
||||
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
|
||||
Handle<PromiseCapability> resultCapability, bool* done);
|
||||
|
||||
enum class CombinatorKind { All, AllSettled, Race };
|
||||
enum class CombinatorKind { All, AllSettled, Any, Race };
|
||||
|
||||
// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
|
||||
// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
|
||||
//
|
||||
// Unified implementation of
|
||||
// 25.6.4.1 Promise.all ( iterable )
|
||||
// 25.6.4.3 Promise.race ( iterable )
|
||||
// 25.6.4.2 Promise.allSettled ( iterable )
|
||||
// 25.6.4.4 Promise.race ( iterable )
|
||||
//
|
||||
// Promise.allSettled (Stage 4 proposal)
|
||||
// https://tc39.github.io/proposal-promise-allSettled/
|
||||
// Promise.any (Stage 3 proposal)
|
||||
// https://tc39.es/proposal-promise-any/
|
||||
//
|
||||
// Promise.allSettled ( iterable )
|
||||
// Promise.any ( iterable )
|
||||
static MOZ_MUST_USE bool CommonPromiseCombinator(JSContext* cx, CallArgs& args,
|
||||
CombinatorKind kind) {
|
||||
HandleValue iterable = args.get(0);
|
||||
|
@ -2420,6 +2428,9 @@ static MOZ_MUST_USE bool CommonPromiseCombinator(JSContext* cx, CallArgs& args,
|
|||
case CombinatorKind::AllSettled:
|
||||
message = "Receiver of Promise.allSettled call";
|
||||
break;
|
||||
case CombinatorKind::Any:
|
||||
message = "Receiver of Promise.any call";
|
||||
break;
|
||||
case CombinatorKind::Race:
|
||||
message = "Receiver of Promise.race call";
|
||||
break;
|
||||
|
@ -2453,6 +2464,9 @@ static MOZ_MUST_USE bool CommonPromiseCombinator(JSContext* cx, CallArgs& args,
|
|||
case CombinatorKind::AllSettled:
|
||||
message = "Argument of Promise.allSettled";
|
||||
break;
|
||||
case CombinatorKind::Any:
|
||||
message = "Argument of Promise.any";
|
||||
break;
|
||||
case CombinatorKind::Race:
|
||||
message = "Argument of Promise.race";
|
||||
break;
|
||||
|
@ -2471,6 +2485,9 @@ static MOZ_MUST_USE bool CommonPromiseCombinator(JSContext* cx, CallArgs& args,
|
|||
case CombinatorKind::AllSettled:
|
||||
result = PerformPromiseAllSettled(cx, iter, C, promiseCapability, &done);
|
||||
break;
|
||||
case CombinatorKind::Any:
|
||||
result = PerformPromiseAny(cx, iter, C, promiseCapability, &done);
|
||||
break;
|
||||
case CombinatorKind::Race:
|
||||
result = PerformPromiseRace(cx, iter, C, promiseCapability, &done);
|
||||
break;
|
||||
|
@ -2706,13 +2723,14 @@ static MOZ_MUST_USE JSObject* CommonStaticResolveRejectImpl(
|
|||
|
||||
static bool IsPromiseSpecies(JSContext* cx, JSFunction* species);
|
||||
|
||||
// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
|
||||
// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
|
||||
// 25.6.4.1.1 Runtime Semantics: PerformPromiseAll, steps 5-6 and step 8.
|
||||
// 25.6.4.3.1 Runtime Semantics: PerformPromiseRace, steps 3-5.
|
||||
// 25.6.4.2.1 Runtime Semantics: PerformPromiseAllSettled, steps 5-6 and step 8.
|
||||
// 25.6.4.4.1 Runtime Semantics: PerformPromiseRace, steps 3-5.
|
||||
//
|
||||
// Promise.allSettled (Stage 4 proposal)
|
||||
// https://tc39.github.io/proposal-promise-allSettled/
|
||||
// Runtime Semantics: PerformPromiseAllSettled, steps 5-6 and step 8.
|
||||
// Promise.any (Stage 3 proposal)
|
||||
// https://tc39.es/proposal-promise-any/
|
||||
// Runtime Semantics: PerformPromiseAny, steps 6-8.
|
||||
template <typename T>
|
||||
static MOZ_MUST_USE bool CommonPerformPromiseCombinator(
|
||||
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
|
||||
|
@ -3316,8 +3334,8 @@ static MOZ_MUST_USE bool PerformPromiseRace(
|
|||
|
||||
enum class PromiseAllSettledElementFunctionKind { Resolve, Reject };
|
||||
|
||||
// Promise.allSettled (Stage 4 proposal)
|
||||
// https://tc39.github.io/proposal-promise-allSettled/
|
||||
// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
|
||||
// 25.6.4.2 Promise.allSettled ( iterable )
|
||||
//
|
||||
// Promise.allSettled Resolve Element Functions
|
||||
// Promise.allSettled Reject Element Functions
|
||||
|
@ -3325,8 +3343,8 @@ template <PromiseAllSettledElementFunctionKind Kind>
|
|||
static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
|
||||
// Promise.allSettled (Stage 4 proposal)
|
||||
// https://tc39.github.io/proposal-promise-allSettled/
|
||||
// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
|
||||
// 25.6.4.2 Promise.allSettled ( iterable )
|
||||
//
|
||||
// Promise.allSettled ( iterable )
|
||||
static bool Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) {
|
||||
|
@ -3334,8 +3352,8 @@ static bool Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return CommonPromiseCombinator(cx, args, CombinatorKind::AllSettled);
|
||||
}
|
||||
|
||||
// Promise.allSettled (Stage 4 proposal)
|
||||
// https://tc39.github.io/proposal-promise-allSettled/
|
||||
// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
|
||||
// 25.6.4.2 Promise.allSettled ( iterable )
|
||||
//
|
||||
// PerformPromiseAllSettled ( iteratorRecord, constructor, resultCapability )
|
||||
static MOZ_MUST_USE bool PerformPromiseAllSettled(
|
||||
|
@ -3521,6 +3539,224 @@ static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc,
|
|||
return true;
|
||||
}
|
||||
|
||||
// Promise.any (Stage 3 proposal)
|
||||
// https://tc39.es/proposal-promise-any/
|
||||
//
|
||||
// Promise.any ( iterable )
|
||||
static bool Promise_static_any(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CommonPromiseCombinator(cx, args, CombinatorKind::Any);
|
||||
}
|
||||
|
||||
// Promise.any (Stage 3 proposal)
|
||||
// https://tc39.es/proposal-promise-any/
|
||||
//
|
||||
// Promise.any Reject Element Functions
|
||||
static bool PromiseAnyRejectElementFunction(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
|
||||
// Promise.any (Stage 3 proposal)
|
||||
// https://tc39.es/proposal-promise-any/
|
||||
//
|
||||
// ThrowAggregateError ( errors )
|
||||
static void ThrowAggregateError(JSContext* cx,
|
||||
Handle<PromiseCombinatorElements> errors,
|
||||
HandleObject promise);
|
||||
|
||||
// Promise.any (Stage 3 proposal)
|
||||
// https://tc39.es/proposal-promise-any/
|
||||
//
|
||||
// PerformPromiseAny ( iteratorRecord, constructor, resultCapability )
|
||||
static MOZ_MUST_USE bool PerformPromiseAny(
|
||||
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
|
||||
Handle<PromiseCapability> resultCapability, bool* done) {
|
||||
*done = false;
|
||||
|
||||
// Step 1.
|
||||
MOZ_ASSERT(C->isConstructor());
|
||||
|
||||
// Step 2 (omitted).
|
||||
|
||||
// Step 3.
|
||||
Rooted<PromiseCombinatorElements> errors(cx);
|
||||
if (!NewPromiseCombinatorElements(cx, resultCapability, &errors)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4.
|
||||
// Create our data holder that holds all the things shared across every step
|
||||
// of the iterator. In particular, this holds the remainingElementsCount (as
|
||||
// an integer reserved slot), the array of errors, and the reject function
|
||||
// from our PromiseCapability.
|
||||
Rooted<PromiseCombinatorDataHolder*> dataHolder(cx);
|
||||
dataHolder = PromiseCombinatorDataHolder::New(
|
||||
cx, resultCapability.promise(), errors, resultCapability.reject());
|
||||
if (!dataHolder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5.
|
||||
uint32_t index = 0;
|
||||
|
||||
auto getResolveAndReject = [cx, &resultCapability, &errors, &dataHolder,
|
||||
&index](MutableHandleValue resolveFunVal,
|
||||
MutableHandleValue rejectFunVal) {
|
||||
// Step 8.h.
|
||||
if (!errors.pushUndefined(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 8.j-p.
|
||||
JSFunction* rejectFunc = NewPromiseCombinatorElementFunction(
|
||||
cx, PromiseAnyRejectElementFunction, dataHolder, index);
|
||||
if (!rejectFunc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 8.q.
|
||||
dataHolder->increaseRemainingCount();
|
||||
|
||||
// Step 8.s.
|
||||
index++;
|
||||
MOZ_ASSERT(index > 0);
|
||||
|
||||
resolveFunVal.setObject(*resultCapability.resolve());
|
||||
rejectFunVal.setObject(*rejectFunc);
|
||||
return true;
|
||||
};
|
||||
|
||||
// BlockOnPromise fast path requires the passed onFulfilled function doesn't
|
||||
// return an object value, because otherwise the skipped promise creation is
|
||||
// detectable due to missing property lookups.
|
||||
bool isDefaultResolveFn =
|
||||
IsNativeFunction(resultCapability.resolve(), ResolvePromiseFunction);
|
||||
|
||||
// Steps 6-8.
|
||||
if (!CommonPerformPromiseCombinator(
|
||||
cx, iterator, C, resultCapability.promise(), done, isDefaultResolveFn,
|
||||
getResolveAndReject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 8.d.ii.
|
||||
int32_t remainingCount = dataHolder->decreaseRemainingCount();
|
||||
|
||||
// Step 8.d.iii.
|
||||
if (remainingCount == 0) {
|
||||
ThrowAggregateError(cx, errors, resultCapability.promise());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 8.d.iv.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Promise.any (Stage 3 proposal)
|
||||
// https://tc39.es/proposal-promise-any/
|
||||
//
|
||||
// Promise.any Reject Element Functions
|
||||
static bool PromiseAnyRejectElementFunction(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
HandleValue xVal = args.get(0);
|
||||
|
||||
// Steps 1-5.
|
||||
Rooted<PromiseCombinatorDataHolder*> data(cx);
|
||||
uint32_t index;
|
||||
if (PromiseCombinatorElementFunctionAlreadyCalled(args, &data, &index)) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 6.
|
||||
Rooted<PromiseCombinatorElements> errors(cx);
|
||||
if (!GetPromiseCombinatorElements(cx, data, &errors)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
if (!errors.setElement(cx, index, xVal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 8, 10.
|
||||
uint32_t remainingCount = data->decreaseRemainingCount();
|
||||
|
||||
// Step 11.
|
||||
if (remainingCount == 0) {
|
||||
// Step 7 (Adapted to work with PromiseCombinatorDataHolder's layout).
|
||||
RootedObject rejectFun(cx, data->resolveOrRejectObj());
|
||||
RootedObject promiseObj(cx, data->promiseObj());
|
||||
|
||||
ThrowAggregateError(cx, errors, promiseObj);
|
||||
|
||||
RootedValue reason(cx);
|
||||
if (!MaybeGetAndClearException(cx, &reason)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!RunResolutionFunction(cx, rejectFun, reason, RejectMode, promiseObj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 12.
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Promise.any (Stage 3 proposal)
|
||||
// https://tc39.es/proposal-promise-any/
|
||||
//
|
||||
// ThrowAggregateError ( errors )
|
||||
static void ThrowAggregateError(JSContext* cx,
|
||||
Handle<PromiseCombinatorElements> errors,
|
||||
HandleObject promise) {
|
||||
MOZ_ASSERT(!cx->isExceptionPending());
|
||||
|
||||
// Create the AggregateError in the same realm as the array object.
|
||||
AutoRealm ar(cx, errors.unwrappedArray());
|
||||
|
||||
RootedObject allocationSite(cx);
|
||||
mozilla::Maybe<JS::AutoSetAsyncStackForNewCalls> asyncStack;
|
||||
|
||||
// Provide a more useful error stack if possible: This function is typically
|
||||
// called from Promise job queue, which doesn't have any JS frames on the
|
||||
// stack. So when we create the AggregateError below, its stack property will
|
||||
// be set to the empty string, which makes it harder to debug the error cause.
|
||||
// To avoid this situation set-up an async stack based on the Promise
|
||||
// allocation site, which should point to calling site of |Promise.any|.
|
||||
if (promise->is<PromiseObject>()) {
|
||||
allocationSite = promise->as<PromiseObject>().allocationSite();
|
||||
if (allocationSite) {
|
||||
asyncStack.emplace(
|
||||
cx, allocationSite, "Promise.any",
|
||||
JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::IMPLICIT);
|
||||
}
|
||||
}
|
||||
|
||||
// AutoSetAsyncStackForNewCalls requires a new activation before it takes
|
||||
// effect, so call into the self-hosting helper to set-up new call frames.
|
||||
RootedValue error(cx);
|
||||
if (!GetAggregateError(cx, JSMSG_PROMISE_ANY_REJECTION, &error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// |error| isn't guaranteed to be an AggregateErrorObject in case of OOM.
|
||||
RootedSavedFrame stack(cx);
|
||||
if (error.isObject() && error.toObject().is<AggregateErrorObject>()) {
|
||||
auto* aggregateError = &error.toObject().as<AggregateErrorObject>();
|
||||
aggregateError->setAggregateErrors(errors.unwrappedArray());
|
||||
|
||||
// Adopt the existing saved frames when present.
|
||||
if (JSObject* errorStack = aggregateError->stack()) {
|
||||
stack = &errorStack->as<SavedFrame>();
|
||||
}
|
||||
}
|
||||
|
||||
cx->setPendingException(error, stack);
|
||||
}
|
||||
|
||||
// https://tc39.github.io/ecma262/#sec-promise.reject
|
||||
//
|
||||
// Unified implementation of
|
||||
|
@ -5866,6 +6102,9 @@ static const JSPropertySpec promise_properties[] = {
|
|||
static const JSFunctionSpec promise_static_methods[] = {
|
||||
JS_FN("all", Promise_static_all, 1, 0),
|
||||
JS_FN("allSettled", Promise_static_allSettled, 1, 0),
|
||||
#ifdef NIGHTLY_BUILD
|
||||
JS_FN("any", Promise_static_any, 1, 0),
|
||||
#endif
|
||||
JS_FN("race", Promise_static_race, 1, 0),
|
||||
JS_FN("reject", Promise_reject, 1, 0),
|
||||
JS_FN("resolve", Promise_static_resolve, 1, 0),
|
||||
|
|
|
@ -177,6 +177,15 @@ function GetTypeError(msg) {
|
|||
assert(false, "the catch block should've returned from this function.");
|
||||
}
|
||||
|
||||
function GetAggregateError(msg) {
|
||||
try {
|
||||
FUN_APPLY(ThrowAggregateError, undefined, arguments);
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
assert(false, "the catch block should've returned from this function.");
|
||||
}
|
||||
|
||||
function GetInternalError(msg) {
|
||||
try {
|
||||
FUN_APPLY(ThrowInternalError, undefined, arguments);
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// |jit-test| skip-if: !Promise.any
|
||||
|
||||
function newPromiseCapability() {
|
||||
var resolve, reject, promise = new Promise(function(r1, r2) {
|
||||
resolve = r1;
|
||||
reject = r2;
|
||||
});
|
||||
return {promise, resolve, reject};
|
||||
}
|
||||
|
||||
function neverCalled() {
|
||||
// Quit with non-zero exit code to ensure a test suite error is shown,
|
||||
// even when this function is called within promise handlers which normally
|
||||
// swallow any exceptions.
|
||||
quit(1);
|
||||
}
|
||||
|
||||
var {promise, resolve} = newPromiseCapability();
|
||||
|
||||
var getterCount = 0;
|
||||
|
||||
class P extends Promise {
|
||||
constructor(executor) {
|
||||
var {promise, resolve, reject} = newPromiseCapability();
|
||||
|
||||
executor(function(v) {
|
||||
// Resolve the promise.
|
||||
resolve(v);
|
||||
|
||||
// But then return an object from the resolve function. This object
|
||||
// must be treated as the resolution value for the otherwise
|
||||
// skipped promise which gets created when Promise.prototype.then is
|
||||
// called in PerformPromiseRace.
|
||||
return {
|
||||
get then() {
|
||||
getterCount++;
|
||||
}
|
||||
};
|
||||
}, neverCalled);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Default to the standard Promise.resolve function, so we don't create
|
||||
// another instance of this class when resolving the passed promise objects
|
||||
// in Promise.race.
|
||||
static resolve(v) {
|
||||
return Promise.resolve(v);
|
||||
}
|
||||
}
|
||||
|
||||
P.any([promise]);
|
||||
|
||||
resolve(0);
|
||||
|
||||
drainJobQueue();
|
||||
|
||||
assertEq(getterCount, 1);
|
|
@ -644,6 +644,7 @@ MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCa
|
|||
MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
|
||||
MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
|
||||
MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.")
|
||||
MSG_DEF(JSMSG_PROMISE_ANY_REJECTION, 0, JSEXN_AGGREGATEERR, "No Promise in Promise.any was resolved")
|
||||
|
||||
// Iterator
|
||||
MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of iterator is not callable")
|
||||
|
|
|
@ -791,3 +791,11 @@ bool js::GetTypeError(JSContext* cx, unsigned errorNumber,
|
|||
return CallSelfHostedFunction(cx, cx->names().GetTypeError, NullHandleValue,
|
||||
args, error);
|
||||
}
|
||||
|
||||
bool js::GetAggregateError(JSContext* cx, unsigned errorNumber,
|
||||
MutableHandleValue error) {
|
||||
FixedInvokeArgs<1> args(cx);
|
||||
args[0].set(Int32Value(errorNumber));
|
||||
return CallSelfHostedFunction(cx, cx->names().GetAggregateError,
|
||||
NullHandleValue, args, error);
|
||||
}
|
||||
|
|
|
@ -119,6 +119,8 @@ bool GetInternalError(JSContext* cx, unsigned errorNumber,
|
|||
MutableHandleValue error);
|
||||
bool GetTypeError(JSContext* cx, unsigned errorNumber,
|
||||
MutableHandleValue error);
|
||||
bool GetAggregateError(JSContext* cx, unsigned errorNumber,
|
||||
MutableHandleValue error);
|
||||
|
||||
} // namespace js
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
// |reftest| skip-if(!Promise.any)
|
||||
|
||||
function toMessage(stack) {
|
||||
// Provide the stack string in the error message for debugging.
|
||||
return `[stack: ${stack.replace(/\n/g, "\\n")}]`;
|
||||
}
|
||||
|
||||
// Test when AggregateError isn't created from a Promise Job.
|
||||
{
|
||||
let p = Promise.any([]); // line 10
|
||||
|
||||
p.then(v => {
|
||||
reportCompare(0, 1, "expected error");
|
||||
}, e => {
|
||||
assertEq(e.name, "AggregateError");
|
||||
var {stack} = e;
|
||||
|
||||
assertEq(/^@.+any-stack.js:10/m.test(stack), true, toMessage(stack));
|
||||
});
|
||||
}
|
||||
|
||||
// Same as above, but now with surrounding function context.
|
||||
function testNoJobQueue() {
|
||||
let p = Promise.any([]); // line 24
|
||||
|
||||
p.then(v => {
|
||||
reportCompare(0, 1, "expected error");
|
||||
}, e => {
|
||||
assertEq(e.name, "AggregateError");
|
||||
var {stack} = e;
|
||||
|
||||
assertEq(/^testNoJobQueue@.+any-stack.js:24/m.test(stack), true, toMessage(stack));
|
||||
});
|
||||
}
|
||||
testNoJobQueue();
|
||||
|
||||
// Test when AggregateError is created from a Promise Job.
|
||||
{
|
||||
let rejected = Promise.reject(0);
|
||||
let p = Promise.any([rejected]); // line 40
|
||||
|
||||
p.then(v => {
|
||||
reportCompare(0, 1, "expected error");
|
||||
}, e => {
|
||||
assertEq(e.name, "AggregateError");
|
||||
var {stack} = e;
|
||||
|
||||
assertEq(/^Promise.any\*@.+any-stack.js:40/m.test(stack), true, toMessage(stack));
|
||||
});
|
||||
}
|
||||
|
||||
// Same as above, but now with surrounding function context.
|
||||
function testFromJobQueue() {
|
||||
let rejected = Promise.reject(0);
|
||||
let p = Promise.any([rejected]); // line 55
|
||||
|
||||
p.then(v => {
|
||||
reportCompare(0, 1, "expected error");
|
||||
}, e => {
|
||||
assertEq(e.name, "AggregateError");
|
||||
var {stack} = e;
|
||||
|
||||
assertEq(/^Promise.any\*testFromJobQueue@.+any-stack.js:55/m.test(stack), true, toMessage(stack));
|
||||
});
|
||||
}
|
||||
testFromJobQueue();
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -0,0 +1,78 @@
|
|||
// |reftest| skip-if(!Promise.any)
|
||||
|
||||
// Smoke test for `Promise.any`, test262 should cover the function in
|
||||
// more detail.
|
||||
|
||||
function expectedError() {
|
||||
reportCompare(true, false, "expected error");
|
||||
}
|
||||
|
||||
// Empty elements.
|
||||
Promise.any([]).then(expectedError, e => {
|
||||
assertEq(e instanceof AggregateError, true);
|
||||
assertEq(e.errors.length, 0);
|
||||
});
|
||||
|
||||
// Single element.
|
||||
Promise.any([Promise.resolve(0)]).then(v => {
|
||||
assertEq(v, 0);
|
||||
});
|
||||
Promise.any([Promise.reject(1)]).then(expectedError, e => {
|
||||
assertEq(e instanceof AggregateError, true);
|
||||
assertEq(e.errors.length, 1);
|
||||
assertEq(e.errors[0], 1);
|
||||
});
|
||||
|
||||
// Multiple elements.
|
||||
Promise.any([Promise.resolve(1), Promise.resolve(2)]).then(v => {
|
||||
assertEq(v, 1);
|
||||
});
|
||||
Promise.any([Promise.resolve(3), Promise.reject(4)]).then(v => {
|
||||
assertEq(v, 3);
|
||||
});
|
||||
Promise.any([Promise.reject(5), Promise.resolve(6)]).then(v => {
|
||||
assertEq(v, 6);
|
||||
});
|
||||
Promise.any([Promise.reject(7), Promise.reject(8)]).then(expectedError, e => {
|
||||
assertEq(e instanceof AggregateError, true);
|
||||
assertEq(e.errors.length, 2);
|
||||
assertEq(e.errors[0], 7);
|
||||
assertEq(e.errors[1], 8);
|
||||
});
|
||||
|
||||
// Cross-Realm tests.
|
||||
//
|
||||
// Note: When |g| is a cross-compartment global, Promise.any creates the errors
|
||||
// array and the AggregateError in |g|'s Realm. This doesn't follow the spec, but
|
||||
// the code in js/src/builtin/Promise.cpp claims this is useful when the Promise
|
||||
// compartment is less-privileged. This means for this test we can't use
|
||||
// assertDeepEq below, because the result array/error may have the wrong prototype.
|
||||
let g = newGlobal();
|
||||
|
||||
if (typeof isSameCompartment !== "function") {
|
||||
var isSameCompartment = SpecialPowers.Cu.getJSTestingFunctions().isSameCompartment;
|
||||
}
|
||||
|
||||
// Test wrapping when no `Promise.any Reject Element Function` is called.
|
||||
Promise.any.call(g.Promise, []).then(expectedError, e => {
|
||||
assertEq(e.name, "AggregateError");
|
||||
|
||||
assertEq(isSameCompartment(e, g), true);
|
||||
assertEq(isSameCompartment(e.errors, g), true);
|
||||
|
||||
assertEq(e.errors.length, 0);
|
||||
});
|
||||
|
||||
// Test wrapping in `Promise.any Reject Element Function`.
|
||||
Promise.any.call(g.Promise, [Promise.reject("err")]).then(expectedError, e => {
|
||||
assertEq(e.name, "AggregateError");
|
||||
|
||||
assertEq(isSameCompartment(e, g), true);
|
||||
assertEq(isSameCompartment(e.errors, g), true);
|
||||
|
||||
assertEq(e.errors.length, 1);
|
||||
assertEq(e.errors[0], "err");
|
||||
});
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -185,6 +185,7 @@
|
|||
MACRO(GeneratorReturn, GeneratorReturn, "GeneratorReturn") \
|
||||
MACRO(GeneratorThrow, GeneratorThrow, "GeneratorThrow") \
|
||||
MACRO(get, get, "get") \
|
||||
MACRO(GetAggregateError, GetAggregateError, "GetAggregateError") \
|
||||
MACRO(GetInternalError, GetInternalError, "GetInternalError") \
|
||||
MACRO(getBigInt64, getBigInt64, "getBigInt64") \
|
||||
MACRO(getBigUint64, getBigUint64, "getBigUint64") \
|
||||
|
|
|
@ -391,6 +391,15 @@ static bool intrinsic_ThrowSyntaxError(JSContext* cx, unsigned argc,
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool intrinsic_ThrowAggregateError(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() >= 1);
|
||||
|
||||
ThrowErrorWithType(cx, JSEXN_AGGREGATEERR, args);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool intrinsic_ThrowInternalError(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
@ -2169,6 +2178,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
JS_FN("ThrowRangeError", intrinsic_ThrowRangeError, 4, 0),
|
||||
JS_FN("ThrowTypeError", intrinsic_ThrowTypeError, 4, 0),
|
||||
JS_FN("ThrowSyntaxError", intrinsic_ThrowSyntaxError, 4, 0),
|
||||
JS_FN("ThrowAggregateError", intrinsic_ThrowAggregateError, 4, 0),
|
||||
JS_FN("ThrowInternalError", intrinsic_ThrowInternalError, 4, 0),
|
||||
JS_FN("GetErrorMessage", intrinsic_GetErrorMessage, 1, 0),
|
||||
JS_FN("CreateModuleSyntaxError", intrinsic_CreateModuleSyntaxError, 4, 0),
|
||||
|
|
Загрузка…
Ссылка в новой задаче