Skip to content

Normative: Close sync iterator when async wrapper yields rejection - #2600

Merged
ljharb merged 1 commit into
tc39:mainfrom
mhofman:fix-async-from-sync-close
Feb 27, 2025
Merged

Normative: Close sync iterator when async wrapper yields rejection#2600
ljharb merged 1 commit into
tc39:mainfrom
mhofman:fix-async-from-sync-close

Conversation

@mhofman

@mhofman mhofman commented Dec 17, 2021

Copy link
Copy Markdown
Member

This updates Async-from-Sync Iterator Objects so that the async wrapper closes its sync iterator when the sync iterator yields a rejected promise as value. This also updates the async wrapper to close the sync iterator when throw is called on the wrapper but is missing on the sync iterator, and updates the rejection value in that case to a TypeError to reflect the contract violation. This aligns the async wrapper behavior to the yield* semantics.

Slides presented at TC39 plenary in January 2020.

Close on rejection

A rejected promise as value is transformed by the async wrapper into a rejection, which is considered by consumers of async iterators as a fatal failure of the iterator, and the consumer will not close the iterator in those cases. However, yielding a rejected promise as value is entirely valid for a sync iterator. The wrapper should adapt both expectations and explicitly close the sync iterator it holds when this situation arise.

Currently a sync iterator consumed by a for..await..of loop would not trigger the iterator's close when a rejected promise is yielded, but the equivalent for..of loop awaiting the result would. After this change, the iterator would be closed in both cases. Closes #1849

This change plumbs the sync iterator into AsyncFromSyncIteratorContinuation with instructions to close it on rejection, but not for return calls (as the iterator was already instructed to close), or if the iterator closed on its own (done === true).

Close on missing throw

If throw is missing on the sync iterator, the async wrapper currently simply rejects with the value given to throw. This deviates from the yield * behavior in 2 ways: the wrapped iterator is not closed, and the rejection value not a TypeError to indicate the contract was broken. This updates fixes both differences by closing the iterator, and throwing a new TypeError instance instead of the value provided to throw.

Since the spec never calls throw on an iterator on its own (it only ever forwards it), and that the async wrapper is never exposed to the program, the only way to observe this async wrapper behavior is through a program calling yield * with a sync iterator from an async generator, and explicitly call throw on that async iterator.

@mhofman mhofman added needs consensus This needs committee consensus before it can be eligible to be merged. normative change Affects behavior required to correctly evaluate some ECMAScript source text labels Dec 17, 2021
@mhofman
mhofman force-pushed the fix-async-from-sync-close branch 2 times, most recently from e5c1f81 to 92262e3 Compare December 17, 2021 03:06
@bakkot bakkot added the needs test262 tests The proposal should specify how to test an implementation. Ideally via github.com/tc39/test262 label Dec 17, 2021
@mhofman
mhofman marked this pull request as ready for review December 17, 2021 03:43
Comment thread spec.html Outdated

@jridgewell jridgewell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's one more case with step 5 and 6:

5. Let valueWrapper be PromiseResolve(%Promise%, value).
6. IfAbruptRejectPromise(valueWrapper, promiseCapability).

The iterator is returning a value, which may throw when we try to coerce into a native promise. Eg,

function* gen() {
  try {
    const p = Promise.resolve('FAIL');
    Object.defineProperty(p, 'constructor', {
      get() {
        throw new Error('foo');
      }
    });
    yield p;
  } finally {
    console.log('PASS');
  }
}

(async () => {
  for await (const v of gen()) {
    console.log(v);
  }
})()

This should call the finally, but doesn't.

Comment thread spec.html Outdated
1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « a newly created *TypeError* object »).
1. Return _promiseCapability_.[[Promise]].
1. Return ! AsyncFromSyncIteratorContinuation(_result_, _promiseCapability_).
1. Return ! AsyncFromSyncIteratorContinuation(_result_, _promiseCapability_, _syncIteratorRecord_, *false*).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't you need to close here? Can you recover from a recovery?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I find it hard to know what the "right" answer is for throw, since it doesn't come up as much (the user has to call it explicitly), but I think you're right. Compare, for example,

function* count() {
  try {
    for (let i = 0; i < 10; ++i) {
      try {
        yield i;
      } catch (e) {
        console.log("caught and suppressed", e);
      }
    }
  } finally {
    console.log("time to clean up");
  }
}

function* wrap(inner) {
  yield* inner;
}

let iterable = wrap(count());
for (let item of iterable) {
  console.log(item);
  console.log("throw returned", iterable.throw("throwing here")); // note that this advances the iterator
  if (item >= 4) break;
}

This goes through three iterations of the loop, printing "caught and suppressed throwing here" in each one, and then on the last iteration, which breaks, also prints "time to clean up".

(The call to wrap in this example has no observable effects, vs just iterable = count() which is my point - calling .throw on this wrapper delegates it to the inner iterator, which suppresses the exception, so the loop continues and can later call .return.)


I'm not at all clear on when you'd ever want to do call throw - I think it was copied from Python, which uses specific kinds of exceptions for more general signals in a way that JS does not, but this was before my time and I haven't reviewed all the relevant notes. But if we assume the analogy above is reasonable, then I agree with @jridgewell.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I totally forgot about throw being able to recover. And I agree, letting the sync iterator decide is the right approach. Good thing is, since we now check the done value before installing the close on rejection logic, just switching the boolean to true here should be sufficient.

Which begs the question, should return be handled similarly? It seems that the result of return can similarly continue a yield * from my reading of the steps (7.c.viii.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which begs the question, should return be handled similarly?

Huh. I guess so, looking at it, though I think this matters less - as these slides say, failure to stop iterating when return is called is probably a bug in the contract between the iterator and the loop.

(It would allow dropping the closeIteratorOnRejection parameter entirely, which is nice.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on

  • In short: any abrupt completion of the loop.
  • Normal completion should not call the method; in
    that case the iterator itself decided to close

I'm willing to say, let's close if the sync iterator doesn't believe it's done, but the consumer of the wrapper considers the async iterator is buggy, regardless of what caused the iterator result to be yielded.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except to forward on explicit calls to throw from the user

Yeah, and this is actually another inconsistency of this wrapper. If sync iterator doesn't have a throw method, the wrapper doesn't fallback to .return like yield * would.

Here we would be calling it twice. That seems like it would violate the expectation that it gets called exactly once. It's also not clear what benefit it would have: the language calls return to say "I'm done with this", but we've already done that the first time we called return, and it's not like we're going to be any more done just because the promise rejected.

Yeah I think I agree.

@bakkot, what do you think of still calling .return if .throw returns a rejected promise as value with done: false, but not calling .return a second time if .return was already called, regardless if it returned done: false or not. In that case I can reintroduce the boolean flag and name it something like "returnAlreadyCalled".

Also, do you think we should add a .return fallback in the wrapper's .throw?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have much intuition for how throw is supposed to work, to be honest.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have much intuition for how throw is supposed to work, to be honest.

Step 7.b.3 of yield * falls back to calling .return if throw is undefined on the iterator. The problem is that the wrapper has a throw on its prototype regardless of the shape of the sync iterator, so yield * would call the wrapper's .throw, which would simply reject because it can't find a .throw on the sync iterator, and not call .return like yield * would have if the wrapper lacked a .throw.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I guess the analogy to yield* is compelling. I'm on board with both

  • if throw is called on the outer iterator and the inner iterator lacks throw, fall back to return
  • if throw is called on the outer iterator and the inner iterator has throw and it returns { done: false, value: Promise.reject() }, call return on the inner iterator.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bakkot's suggestion SGTM.

@mhofman

mhofman commented Dec 17, 2021

Copy link
Copy Markdown
Member Author

The iterator is returning a value, which may throw when we try to coerce into a native promise.

In principle I'd say yes, but this will complicate things quite a bit, and arguably we have an iterator yielding bogus values

@bakkot

bakkot commented Dec 17, 2021

Copy link
Copy Markdown
Member

In principle I'd say yes, but this will complicate things quite a bit

Is it not sufficient to add 1. If _valueWrapper_ is an abrupt completion, set _valueWrapper_ to IteratorClose(_syncIteratorRecord_, _valueWrapper_) before step 6? That's not very much complexity.

@mhofman

mhofman commented Dec 17, 2021

Copy link
Copy Markdown
Member Author

But only if closeIteratorOnRejection and not done, right ?

@bakkot

bakkot commented Dec 17, 2021

Copy link
Copy Markdown
Member

Sorry, right.

Comment thread spec.html Outdated
@mhofman

mhofman commented Dec 17, 2021

Copy link
Copy Markdown
Member Author

PTAL @bakkot @jridgewell .

I decided to ignore the operation type when closing the iterator and solely rely on the done value.
If the sync iterator decides it isn't actually done on return, it will get another closing attempt from IteratorClose, but that's fine by me since it's a buggy behavior in the first place on behalf of the sync iterator.

@mhofman
mhofman force-pushed the fix-async-from-sync-close branch from 1400ee6 to f3c4d29 Compare December 21, 2021 18:56
@mhofman
mhofman marked this pull request as draft January 13, 2022 17:52
@mhofman
mhofman force-pushed the fix-async-from-sync-close branch from f3c4d29 to 4dcbf3d Compare January 22, 2022 23:55
@mhofman
mhofman marked this pull request as ready for review January 23, 2022 00:40
@mhofman

mhofman commented Jan 23, 2022

Copy link
Copy Markdown
Member Author

@bakkot @jridgewell, PTAL

I believe this now specifies the behavior we discussed in #2600 (comment)

We could consider rejecting with TypeError for missing throw like yield *.

This is yet another discrepancy I found, but I'm less sure if we should fix it.

@bakkot bakkot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM other than comment.

Comment thread spec.html Outdated
Comment thread spec.html Outdated
Comment thread spec.html Outdated
@jmdyck

jmdyck commented Jan 25, 2022

Copy link
Copy Markdown
Collaborator

Looks okay to me.

Note that AsyncFromSyncIteratorContinuation's new _syncIteratorRecord_ parameter would be an Iterator Record in PR #2591, so whichever lands second should update.

@ljharb ljharb added has consensus This has committee consensus. es2022 and removed needs consensus This needs committee consensus before it can be eligible to be merged. labels Jan 25, 2022
@mhofman
mhofman force-pushed the fix-async-from-sync-close branch from c85d807 to da31741 Compare January 25, 2022 21:08
@vadzim

vadzim commented Dec 13, 2024

Copy link
Copy Markdown

@mhofman

First of all thank you a lot for your work. Recent Chrome and Node already have this fix.

I've just realized that just closing sync iterator causes different behavior with try/catch within async and sync generators

like

void async function () {
  try {
    for await (const num of function*() {
      try {
        yield 1;
        yield Promise.reject(2);
        yield 3;
      } catch (e) {
        console.log("called catch", e);
        throw e;
      } finally {
        console.log("called finally");
      }
    }()) {
      console.log(num);
    }
  } catch (e) {
    console.log("caught", e);
  }
}()
// 1
// called finally
// caught 2

and

void async function () {
  try {
    for await (const num of /*->*/async/*<- the only difference*/ function*() {
      try {
        yield 1;
        yield Promise.reject(2);
        yield 3;
      } catch (e) {
        console.log("called catch", e);
        throw e;
      } finally {
        console.log("called finally");
      }
    }()) {
      console.log(num);
    }
  } catch (e) {
    console.log("caught", e);
  }
}()
// 1
// called catch 2
// called finally
// caught 2

In particular that means that async iterator can catch the error, handle it and continue to run while sync iterator cannot.

I would personally prefer that AsyncFromSyncIteratorContinuation makes sync iterator behavior exactly the same as async one and not to explain my grandchild why even recent JS features are so damn strange. But I'm not really sure if it's worth to spend someone's time to fix that.

@mhofman

mhofman commented Dec 14, 2024

Copy link
Copy Markdown
Member Author

In particular that means that async iterator can catch the error, handle it and continue to run while sync iterator cannot.

I would personally prefer that AsyncFromSyncIteratorContinuation makes sync iterator behavior exactly the same as async one and not to explain my grandchild why even recent JS features are so damn strange. But I'm not really sure if it's worth to spend someone's time to fix that.

This is a potentially more complicated problem. In the case of the async generator, the catch is triggered because the yield does an implicit await of the yielded value. The spec currently does not ever invoke throw on its own on an iterator: it only passes it through in the case of yield * or when a async-from-sync iterator has its throw called.

I do not recall if this was explicitly discussed in plenary, but technically we could attempt to invoke the throw of the sync iterator if it exists in the onRejected handler. This would be another normative change.

@vadzim

vadzim commented Dec 16, 2024

Copy link
Copy Markdown

technically we could attempt to invoke the throw of the sync iterator if it exists in the onRejected handler

yep, sync-to-async wrapper already awaits the sync generator's yielded values simulating the implicit awaits within an async generator.
just it does not simulate throwing an exception.

webkit-commit-queue pushed a commit to sosukesuzuki/WebKit that referenced this pull request Feb 3, 2025
…row` is null or undefined

https://bugs.webkit.org/show_bug.cgi?id=286885

Reviewed by Yusuke Suzuki.

The normative change for `AsyncFromSyncIterator`[1] includes the following two changes:

1. AsyncFromSyncIterator now closes the sync iterator when `throw` is called on the
wrapper but is not implemented on the sync iterator. In that case, it updates the
rejection value to a `TypeError` to reflect the contract violation.
2. AsyncFromSyncIterator closes its sync iterator when the sync iterator yields a
rejected promise as its value.

This normative change has already been implemented in V8[2]. This patch changes to
implement only the first change.

[1]: tc39/ecma262#2600
[2]: v8/v8@6c3f7aa#diff-c18e5d5743326f6ba544801f6ce25154b785f759885651f98924d5b56dc1c2e6

* JSTests/stress/async-from-sync-iterator-prototype-throw-close-sync-iter.js: Added.
(shouldBe):
(shouldThrowAsync):
(throw.new.Error.const.syncIterator.get throw):
(throw.new.Error.const.asyncIterator):
(throw.new.Error.async OurError):
(throw.new.Error):
(const.syncIterator.get throw):
(throw.new.Error.async drainMicrotasks):
(const.asyncIterator):
(async OurError):
(OurError):
(NotOurError):
(const.syncIterator.get return):
(async drainMicrotasks):
(async asyncIterator):
* JSTests/test262/expectations.yaml:
* Source/JavaScriptCore/builtins/AsyncFromSyncIteratorPrototype.js:
(throw):

Canonical link: https://commits.webkit.org/289726@main
webkit-commit-queue pushed a commit to sosukesuzuki/WebKit that referenced this pull request Feb 10, 2025
…xt`/`throw` yields rejected promise

https://bugs.webkit.org/show_bug.cgi?id=273768

Reviewed by Yusuke Suzuki.

The normative change for `AsyncFromSyncIterator`[1] includes the following two changes:

1. AsyncFromSyncIterator now closes the sync iterator when `throw` is called on the
wrapper but is not implemented on the sync iterator. In that case, it updates the
rejection value to a `TypeError` to reflect the contract violation.
2. AsyncFromSyncIterator closes its sync iterator when the sync iterator yields a
rejected promise as its value.

This normative change has already been implemented in V8[2]. This patch changes to
implement only the second change.

[1]: tc39/ecma262#2600
[2]: v8/v8@6c3f7aa#diff-c18e5d5743326f6ba544801f6ce25154b785f759885651f98924d5b56dc1c2e6

* JSTests/stress/async-from-sync-iterator-prototype-next-rejected-close-sync-iter.js: Added.
(shouldBe):
(shouldThrowAsync):
(throw.new.Error.OurError):
(throw.new.Error.iterator):
(OurError):
(throw.new.Error.async drainMicrotasks):
(iterator):
(async asyncIterator):
(async drainMicrotasks):
* JSTests/stress/async-from-sync-iterator-prototype-throw-rejected-close-sync-iter.js: Added.
(shouldBe):
(shouldThrowAsync):
(throw.new.Error.OurError):
(throw.new.Error.async asyncIterator):
* JSTests/test262/expectations.yaml:
* Source/JavaScriptCore/builtins/AsyncFromSyncIteratorPrototype.js:
(linkTimeConstant.asyncFromSyncIteratorOnRejected):
(linkTimeConstant.asyncFromSyncIteratorOnFulfilledContinue):
(linkTimeConstant.asyncFromSyncIteratorOnFulfilledDone):
(return):
(throw):
* Source/JavaScriptCore/builtins/BuiltinNames.h:

Canonical link: https://commits.webkit.org/290137@main
@sosukesuzuki

Copy link
Copy Markdown

@bakkot bakkot added the editor call to be discussed in the next editor call label Feb 10, 2025
@bakkot bakkot added ready to merge Editors believe this PR needs no further reviews, and is ready to land. and removed editor call to be discussed in the next editor call labels Feb 26, 2025
@ljharb
ljharb force-pushed the fix-async-from-sync-close branch from f42aea0 to ff129b1 Compare February 27, 2025 00:01
@ljharb
ljharb dismissed jmdyck’s stale review February 27, 2025 00:02

changes addressed

@ljharb
ljharb merged commit ff129b1 into tc39:main Feb 27, 2025
@vadzim

vadzim commented Mar 3, 2025

Copy link
Copy Markdown

Sorry, guys
I'm just not sure
Do those PRs also resolve the issue described in #2600 (comment) ?
@mhofman
@sosukesuzuki

@bakkot

bakkot commented Mar 3, 2025

Copy link
Copy Markdown
Member

@vadzim No, no one has proposed to change that behavior (and I would be opposed to changing it - that looks like the right behavior to me).

@mhofman

mhofman commented Mar 3, 2025

Copy link
Copy Markdown
Member Author

Do those PRs also resolve the issue described in #2600 (comment) ?

It does not.

According to #1849 (comment), there are opinions to not have the synthetic throw behavior you propose. You could open a new issue, but that change would be harder to justify.

I am personally ambivalent, but it's not really clear what the behavior should be if throw exists, and e.g. the catch clause doesn't retrow but instead yields more values.

js-choi added a commit to js-choi/test262 that referenced this pull request Apr 8, 2025
Adds
built-ins/Array/fromAsync/sync-iterable-with-rejecting-thenable-closes.js.

This is
related to tc39/ecma262#1849 and tc39/ecma262#2600.
This closes tc39/proposal-array-from-async#49.

Renames
built-ins/Array/fromAsync/sync-iterable-with-thenable-element-rejects.js
to
built-ins/Array/fromAsync/sync-iterable-with-rejecting-thenable-closes.js
for consistency with the new test.
infonl-marcel pushed a commit to infonl/zgw-office-addin that referenced this pull request May 1, 2025
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [core-js](https://redirect.github.com/zloirock/core-js)
([source](https://redirect.github.com/zloirock/core-js/tree/HEAD/packages/core-js))
| [`3.41.0` ->
`3.42.0`](https://renovatebot.com/diffs/npm/core-js/3.41.0/3.42.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/core-js/3.42.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/core-js/3.42.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/core-js/3.41.0/3.42.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/core-js/3.41.0/3.42.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>zloirock/core-js (core-js)</summary>

###
[`v3.42.0`](https://redirect.github.com/zloirock/core-js/blob/HEAD/CHANGELOG.md#3420---20250430)

[Compare
Source](https://redirect.github.com/zloirock/core-js/compare/v3.41.0...v3.42.0)

- Changes
[v3.41.0...v3.42.0](https://redirect.github.com/zloirock/core-js/compare/v3.41.0...v3.42.0)
(142 commits)
- [`Map` upsert
proposal](https://redirect.github.com/tc39/proposal-upsert):
- Moved to stage 2.7, [April 2025 TC39
meeting](https://x.com/robpalmer2/status/1911882240109261148)
- Validation order of `WeakMap.prototype.getOrInsertComputed` updated
following
[tc39/proposal-upsert#79](https://redirect.github.com/tc39/proposal-upsert/pull/79)
    -   Built-ins:
        -   `Map.prototype.getOrInsert`
        -   `Map.prototype.getOrInsertComputed`
        -   `WeakMap.prototype.getOrInsert`
        -   `WeakMap.prototype.getOrInsertComputed`
- Don't call well-known `Symbol` methods for `RegExp` on primitive
values following
[tc39/ecma262#3009](https://redirect.github.com/tc39/ecma262/pull/3009):
- For avoid performance regression, temporarily, only in own `core-js`
implementations
    -   Built-ins:
        -   `String.prototype.matchAll`
        -   `String.prototype.match`
        -   `String.prototype.replaceAll`
        -   `String.prototype.replace`
        -   `String.prototype.search`
        -   `String.prototype.split`
- Added workaround for the
[`Uint8Array.prototype.setFromBase64`](https://redirect.github.com/tc39/proposal-arraybuffer-base64)
[bug](https://bugs.webkit.org/show_bug.cgi?id=290829) in some of Linux
builds of WebKit
- Implemented early-error iterator closing following
[tc39/ecma262#3467](https://redirect.github.com/tc39/ecma262/pull/3467),
including fix of [a WebKit
bug](https://bugs.webkit.org/show_bug.cgi?id=291195), in the following
methods:
    -   `Iterator.prototype.drop`
    -   `Iterator.prototype.every`
    -   `Iterator.prototype.filter`
    -   `Iterator.prototype.find`
    -   `Iterator.prototype.flatMap`
    -   `Iterator.prototype.forEach`
    -   `Iterator.prototype.map`
    -   `Iterator.prototype.reduce`
    -   `Iterator.prototype.some`
    -   `Iterator.prototype.take`
- Fixed missing forced replacement of [`AsyncIterator`
helpers](https://redirect.github.com/tc39/proposal-async-iterator-helpers)
- Added closing of sync iterator when async wrapper yields a rejection
following
[tc39/ecma262#2600](https://redirect.github.com/tc39/ecma262/pull/2600).
Affected methods:
-
[`Array.fromAsync`](https://redirect.github.com/tc39/proposal-array-from-async)
(due to the lack of async feature detection capability - temporarily,
only in own `core-js` implementation)
-
[`AsyncIterator.from`](https://redirect.github.com/tc39/proposal-async-iterator-helpers)
-
[`Iterator.prototype.toAsync`](https://redirect.github.com/tc39/proposal-async-iterator-helpers)
- Added detection for throwing on `undefined` initial parameter in
`Iterator.prototype.reduce` (see [WebKit
bug](https://bugs.webkit.org/show_bug.cgi?id=291651))
-   `core-js-compat` and `core-js-builder` API:
- Added `'intersect'` support for `targets.esmodules` (Babel 7 behavior)
    -   Fixed handling of `targets.esmodules: true` (Babel 7 behavior)
-   Compat data improvements:
- [Explicit Resource
Management](https://redirect.github.com/tc39/proposal-explicit-resource-management)
features disabled (again) in V8 ~ Chromium 135 and re-added in 136
-
[`RegExp.escape`](https://redirect.github.com/tc39/proposal-regex-escaping)
marked as [shipped from V8 ~ Chromium
136](https://issues.chromium.org/issues/353856236#comment17)
- [`Error.isError`](https://redirect.github.com/tc39/proposal-is-error)
marked as [shipped from
FF138](https://bugzilla.mozilla.org/show_bug.cgi?id=1952249)
- [Explicit Resource
Management](https://redirect.github.com/tc39/proposal-explicit-resource-management)
features re-enabled in [Deno
2.2.10](https://redirect.github.com/denoland/deno/releases/tag/v2.2.10)
- [`Iterator` helpers
proposal](https://redirect.github.com/tc39/proposal-iterator-helpers)
features marked as supported from Deno 1.38.1 since it seems they were
disabled in 1.38.0
- `Iterator.prototype.{ drop, reduce, take }` methods marked as fixed in
Bun 1.2.11
- Added [NodeJS
24.0](https://redirect.github.com/nodejs/node/pull/57609) compat data
mapping
    -   Updated Electron 36 and added Electron 37 compat data mapping
- Added Opera Android
[88](https://forums.opera.com/topic/83800/opera-for-android-88) and
[89](https://forums.opera.com/topic/84437/opera-for-android-89) compat
data mapping
    -   Added Oculus Quest Browser 37 compat data mapping

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/infonl/podiumd-office-plugin).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yNTcuMyIsInVwZGF0ZWRJblZlciI6IjM5LjI1Ny4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

has consensus This has committee consensus. has test262 tests normative change Affects behavior required to correctly evaluate some ECMAScript source text ready to merge Editors believe this PR needs no further reviews, and is ready to land.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finally is not called when asynchronously iterating over synchronous generator which yields rejected promise