Commit 1f6d27e8 authored by Sathya Gunasekaran's avatar Sathya Gunasekaran Committed by Commit Bot

[ESNext] Implement Promise.allSettled

Bug: v8:9060
Change-Id: Ia58f7f9e19726f26dd09665d32efc1037f71e7e2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1560409
Commit-Queue: Sathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarMathias Bynens <mathias@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60830}
parent b151cd2f
...@@ -4359,6 +4359,26 @@ void Genesis::InitializeGlobal_harmony_weak_refs() { ...@@ -4359,6 +4359,26 @@ void Genesis::InitializeGlobal_harmony_weak_refs() {
} }
} }
void Genesis::InitializeGlobal_harmony_promise_all_settled() {
if (!FLAG_harmony_promise_all_settled) return;
SimpleInstallFunction(isolate(), isolate()->promise_function(), "allSettled",
Builtins::kPromiseAllSettled, 1, false);
Factory* factory = isolate()->factory();
{
Handle<SharedFunctionInfo> info = SimpleCreateSharedFunctionInfo(
isolate_, Builtins::kPromiseAllSettledResolveElementClosure,
factory->empty_string(), 1);
native_context()->set_promise_all_settled_resolve_element_shared_fun(*info);
}
{
Handle<SharedFunctionInfo> info = SimpleCreateSharedFunctionInfo(
isolate_, Builtins::kPromiseAllSettledRejectElementClosure,
factory->empty_string(), 1);
native_context()->set_promise_all_settled_reject_element_shared_fun(*info);
}
}
#ifdef V8_INTL_SUPPORT #ifdef V8_INTL_SUPPORT
void Genesis::InitializeGlobal_harmony_intl_date_format_range() { void Genesis::InitializeGlobal_harmony_intl_date_format_range() {
......
...@@ -858,6 +858,10 @@ namespace internal { ...@@ -858,6 +858,10 @@ namespace internal {
TFJ(PromiseAllResolveElementClosure, 1, kReceiver, kValue) \ TFJ(PromiseAllResolveElementClosure, 1, kReceiver, kValue) \
/* ES #sec-promise.race */ \ /* ES #sec-promise.race */ \
TFJ(PromiseRace, 1, kReceiver, kIterable) \ TFJ(PromiseRace, 1, kReceiver, kIterable) \
/* ES #sec-promise.allsettled */ \
TFJ(PromiseAllSettled, 1, kReceiver, kIterable) \
TFJ(PromiseAllSettledResolveElementClosure, 1, kReceiver, kValue) \
TFJ(PromiseAllSettledRejectElementClosure, 1, kReceiver, kValue) \
/* V8 Extras: v8.createPromise(parent) */ \ /* V8 Extras: v8.createPromise(parent) */ \
TFJ(PromiseInternalConstructor, 1, kReceiver, kParent) \ TFJ(PromiseInternalConstructor, 1, kReceiver, kParent) \
/* V8 Extras: v8.rejectPromise(promise, reason) */ \ /* V8 Extras: v8.rejectPromise(promise, reason) */ \
......
...@@ -2135,10 +2135,8 @@ Node* PromiseBuiltinsAssembler::PerformPromiseAll( ...@@ -2135,10 +2135,8 @@ Node* PromiseBuiltinsAssembler::PerformPromiseAll(
// Register the PromiseReaction immediately on the {next_value}, not // Register the PromiseReaction immediately on the {next_value}, not
// passing any chained promise since neither async_hooks nor DevTools // passing any chained promise since neither async_hooks nor DevTools
// are enabled, so there's no use of the resulting promise. // are enabled, so there's no use of the resulting promise.
PerformPromiseThen( PerformPromiseThen(native_context, next_value, resolve_element_fun,
native_context, next_value, resolve_element_fun, reject_element_fun, UndefinedConstant());
LoadObjectField(capability, PromiseCapability::kRejectOffset),
UndefinedConstant());
Goto(&loop); Goto(&loop);
} }
...@@ -2337,6 +2335,30 @@ TF_BUILTIN(PromiseAll, PromiseBuiltinsAssembler) { ...@@ -2337,6 +2335,30 @@ TF_BUILTIN(PromiseAll, PromiseBuiltinsAssembler) {
}); });
} }
// ES#sec-promise.allsettled
// Promise.allSettled ( iterable )
TF_BUILTIN(PromiseAllSettled, PromiseBuiltinsAssembler) {
TNode<Object> receiver = Cast(Parameter(Descriptor::kReceiver));
TNode<Context> context = Cast(Parameter(Descriptor::kContext));
TNode<Object> iterable = Cast(Parameter(Descriptor::kIterable));
Generate_PromiseAll(
context, receiver, iterable,
[this](TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability) {
return CreatePromiseAllResolveElementFunction(
context, index, native_context,
Context::PROMISE_ALL_SETTLED_RESOLVE_ELEMENT_SHARED_FUN);
},
[this](TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability) {
return CreatePromiseAllResolveElementFunction(
context, index, native_context,
Context::PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN);
});
}
void PromiseBuiltinsAssembler::Generate_PromiseAllResolveElementClosure( void PromiseBuiltinsAssembler::Generate_PromiseAllResolveElementClosure(
TNode<Context> context, TNode<Object> value, TNode<JSFunction> function, TNode<Context> context, TNode<Object> value, TNode<JSFunction> function,
const CreatePromiseAllResolveElementFunctionValue& callback) { const CreatePromiseAllResolveElementFunctionValue& callback) {
...@@ -2352,12 +2374,12 @@ void PromiseBuiltinsAssembler::Generate_PromiseAllResolveElementClosure( ...@@ -2352,12 +2374,12 @@ void PromiseBuiltinsAssembler::Generate_PromiseAllResolveElementClosure(
this, this,
SmiEqual(LoadObjectField<Smi>(context, Context::kLengthOffset), SmiEqual(LoadObjectField<Smi>(context, Context::kLengthOffset),
SmiConstant(PromiseBuiltins::kPromiseAllResolveElementLength))); SmiConstant(PromiseBuiltins::kPromiseAllResolveElementLength)));
TNode<Context> native_context = LoadNativeContext(context); TNode<NativeContext> native_context = Cast(LoadNativeContext(context));
StoreObjectField(function, JSFunction::kContextOffset, native_context); StoreObjectField(function, JSFunction::kContextOffset, native_context);
// Update the value depending on whether Promise.all or // Update the value depending on whether Promise.all or
// Promise.allSettled is called. // Promise.allSettled is called.
value = callback(context, value); value = callback(context, native_context, value);
// Determine the index from the {function}. // Determine the index from the {function}.
Label unreachable(this, Label::kDeferred); Label unreachable(this, Label::kDeferred);
...@@ -2460,7 +2482,71 @@ TF_BUILTIN(PromiseAllResolveElementClosure, PromiseBuiltinsAssembler) { ...@@ -2460,7 +2482,71 @@ TF_BUILTIN(PromiseAllResolveElementClosure, PromiseBuiltinsAssembler) {
Generate_PromiseAllResolveElementClosure( Generate_PromiseAllResolveElementClosure(
context, value, function, context, value, function,
[](TNode<Object> context, TNode<Object> value) { return value; }); [](TNode<Object>, TNode<NativeContext>, TNode<Object> value) {
return value;
});
}
TF_BUILTIN(PromiseAllSettledResolveElementClosure, PromiseBuiltinsAssembler) {
TNode<Object> value = CAST(Parameter(Descriptor::kValue));
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<JSFunction> function = CAST(Parameter(Descriptor::kJSTarget));
Generate_PromiseAllResolveElementClosure(
context, value, function,
[this](TNode<Context> context, TNode<NativeContext> native_context,
TNode<Object> value) {
// TODO(gsathya): Optimize the creation using a cached map to
// prevent transitions here.
// 9. Let obj be ! ObjectCreate(%ObjectPrototype%).
TNode<HeapObject> object_function = Cast(
LoadContextElement(native_context, Context::OBJECT_FUNCTION_INDEX));
TNode<Map> object_function_map = Cast(LoadObjectField(
object_function, JSFunction::kPrototypeOrInitialMapOffset));
TNode<JSObject> obj =
Cast(AllocateJSObjectFromMap(object_function_map));
// 10. Perform ! CreateDataProperty(obj, "status", "fulfilled").
CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
StringConstant("status"), StringConstant("fulfilled"));
// 11. Perform ! CreateDataProperty(obj, "value", x).
CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
StringConstant("value"), value);
return obj;
});
}
TF_BUILTIN(PromiseAllSettledRejectElementClosure, PromiseBuiltinsAssembler) {
TNode<Object> value = CAST(Parameter(Descriptor::kValue));
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<JSFunction> function = CAST(Parameter(Descriptor::kJSTarget));
Generate_PromiseAllResolveElementClosure(
context, value, function,
[this](TNode<Context> context, TNode<NativeContext> native_context,
TNode<Object> value) {
// TODO(gsathya): Optimize the creation using a cached map to
// prevent transitions here.
// 9. Let obj be ! ObjectCreate(%ObjectPrototype%).
TNode<HeapObject> object_function = Cast(
LoadContextElement(native_context, Context::OBJECT_FUNCTION_INDEX));
TNode<Map> object_function_map = Cast(LoadObjectField(
object_function, JSFunction::kPrototypeOrInitialMapOffset));
TNode<JSObject> obj =
Cast(AllocateJSObjectFromMap(object_function_map));
// 10. Perform ! CreateDataProperty(obj, "status", "rejected").
CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
StringConstant("status"), StringConstant("rejected"));
// 11. Perform ! CreateDataProperty(obj, "reason", x).
CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
StringConstant("reason"), value);
return obj;
});
} }
// ES#sec-promise.race // ES#sec-promise.race
......
...@@ -171,6 +171,7 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -171,6 +171,7 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler {
const PromiseAllResolvingElementFunction& create_reject_element_function); const PromiseAllResolvingElementFunction& create_reject_element_function);
typedef std::function<TNode<Object>(TNode<Context> context, typedef std::function<TNode<Object>(TNode<Context> context,
TNode<NativeContext> native_context,
TNode<Object> value)> TNode<Object> value)>
CreatePromiseAllResolveElementFunctionValue; CreatePromiseAllResolveElementFunctionValue;
......
...@@ -230,6 +230,10 @@ enum ContextLookupFlags { ...@@ -230,6 +230,10 @@ enum ContextLookupFlags {
promise_thrower_finally_shared_fun) \ promise_thrower_finally_shared_fun) \
V(PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN, SharedFunctionInfo, \ V(PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN, SharedFunctionInfo, \
promise_all_resolve_element_shared_fun) \ promise_all_resolve_element_shared_fun) \
V(PROMISE_ALL_SETTLED_RESOLVE_ELEMENT_SHARED_FUN, SharedFunctionInfo, \
promise_all_settled_resolve_element_shared_fun) \
V(PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN, SharedFunctionInfo, \
promise_all_settled_reject_element_shared_fun) \
V(PROMISE_PROTOTYPE_INDEX, JSObject, promise_prototype) \ V(PROMISE_PROTOTYPE_INDEX, JSObject, promise_prototype) \
V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \ V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \
V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \ V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \
......
...@@ -218,6 +218,7 @@ DEFINE_IMPLICATION(harmony_private_methods, harmony_private_fields) ...@@ -218,6 +218,7 @@ DEFINE_IMPLICATION(harmony_private_methods, harmony_private_fields)
V(harmony_private_methods, "harmony private methods in class literals") \ V(harmony_private_methods, "harmony private methods in class literals") \
V(harmony_regexp_sequence, "RegExp Unicode sequence properties") \ V(harmony_regexp_sequence, "RegExp Unicode sequence properties") \
V(harmony_weak_refs, "harmony weak references") \ V(harmony_weak_refs, "harmony weak references") \
V(harmony_promise_all_settled, "harmony Promise.allSettled")
#ifdef V8_INTL_SUPPORT #ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) \ #define HARMONY_INPROGRESS(V) \
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --harmony-promise-all-settled
class MyError extends Error {}
const descriptor = Object.getOwnPropertyDescriptor(Promise, "allSettled");
assertTrue(descriptor.configurable);
assertTrue(descriptor.writable);
assertFalse(descriptor.enumerable);
// 1. Let C be the this value.
// 2. If Type(C) is not Object, throw a TypeError exception.
assertThrows(() => Promise.allSettled.call(1), TypeError);
{
// 3. Let promiseCapability be ? NewPromiseCapability(C).
let called = false;
class MyPromise extends Promise {
constructor(...args) {
called = true;
super(...args);
}
}
MyPromise.allSettled([]);
assertTrue(called);
}
{
// 4. Let iteratorRecord be GetIterator(iterable).
// 5. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
let caught = false;
(async function() {
class MyError extends Error {}
let err;
try {
await Promise.allSettled({
[Symbol.iterator]() {
throw new MyError();
}
});
} catch (e) {
assertTrue(e instanceof MyError);
caught = true;
}
})();
%PerformMicrotaskCheckpoint();
assertTrue(caught);
}
{
// 6. a. Let next be IteratorStep(iteratorRecord).
let iteratorStep = false;
(async function() {
try {
await Promise.allSettled({
[Symbol.iterator]() {
return {
next() {
iteratorStep = true;
return { done: true }
}
};
}
});
} catch (e) {
%AbortJS(e.stack);
}
})();
%PerformMicrotaskCheckpoint();
assertTrue(iteratorStep);
}
{
// 6. a. Let next be IteratorStep(iteratorRecord).
// 6. b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// 6. c. ReturnIfAbrupt(next).
let caught = false;
(async function() {
try {
await Promise.allSettled({
[Symbol.iterator]() {
return {
next() {
throw new MyError();
}
};
}
});
} catch (e) {
assertTrue(e instanceof MyError);
caught = true;
}
})();
%PerformMicrotaskCheckpoint();
assertTrue(caught);
}
{
// 6. e. Let nextValue be IteratorValue(next).
let iteratorValue = false;
(async function() {
try {
await Promise.allSettled({
[Symbol.iterator]() {
let done = false;
return {
next() {
let result = { value: 1, done };
iteratorValue = true;
done = true;
return result;
}
};
}
});
} catch (e) {
%AbortJS(e.stack);
}
})();
%PerformMicrotaskCheckpoint();
assertTrue(iteratorValue);
}
{
// 6. e. Let nextValue be IteratorValue(next).
// 6. f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
// 6. g. ReturnIfAbrupt(nextValue).
let caught = false;
(async function() {
try {
await Promise.allSettled({
[Symbol.iterator]() {
let done = false;
return {
next() {
return result = {
get value() {throw new MyError(''); },
done: false
};
}
};
}
});
} catch (e) {
assertTrue(e instanceof MyError);
caught = true;
}
})();
%PerformMicrotaskCheckpoint();
assertTrue(caught);
}
{
// TODO(mathias): https://github.com/tc39/proposal-promise-allSettled/pull/40
// 6. i. Let nextPromise be ? Invoke(constructor, "resolve", « nextValue »).
let called = false;
class MyPromise extends Promise {
static resolve(...args) {
called = true;
super.resolve(...args);
}
}
MyPromise.allSettled([1]);
assertTrue(called);
}
{
// 6. z. Perform ? Invoke(nextPromise, "then", « resolveElement, rejectElement »).
let called = false;
class MyPromise extends Promise {
then(...args) {
called = true;
super.resolve(...args);
}
}
MyPromise.allSettled([1]);
assertTrue(called);
}
{
let called = false;
let result;
Promise.allSettled([]).then(x => {
called = true;
result = x;
});
%PerformMicrotaskCheckpoint();
assertTrue(called);
assertEquals(result, []);
}
{
let called = false;
Promise.allSettled([Promise.resolve("foo")]).then(v => {
assertEquals(v.length, 1);
const [x] = v;
assertSame(Object.getPrototypeOf(x), Object.getPrototypeOf({}));
const descs = Object.getOwnPropertyDescriptors(x);
assertEquals(Object.keys(descs).length, 2);
const { value: desc } = descs;
assertTrue(desc.writable);
assertTrue(desc.enumerable);
assertTrue(desc.configurable);
assertEquals(x.value, "foo");
assertEquals(x.status, "fulfilled");
called = true;
});
%PerformMicrotaskCheckpoint();
assertTrue(called);
}
{
let called = false;
Promise.allSettled([Promise.reject("foo")]).then(v => {
assertEquals(v.length, 1);
const [x] = v;
assertEquals(x.reason, "foo");
assertEquals(x.status, "rejected");
called = true;
});
%PerformMicrotaskCheckpoint();
assertTrue(called);
}
{
let called = false;
Promise.allSettled([Promise.resolve("bar"), Promise.reject("foo")]).then(v => {
assertEquals(v.length, 2);
const [x, y] = v;
assertEquals(x.value, "bar");
assertEquals(x.status, "fulfilled");
assertEquals(y.reason, "foo");
assertEquals(y.status, "rejected");
called = true;
});
%PerformMicrotaskCheckpoint();
assertTrue(called);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment