Stage 1 Draft / November 9, 2022

ES Array.fromAsync (2022)

Introduction

This is the formal specification for a proposed Array.fromAsync factory method in JavaScript. It modifies the original ECMAScript specification with several new or revised clauses. See the proposal's explainer for the proposal's background, motivation, and usage examples.

1 Control Abstraction Objects

1.1 Iteration

1.1.1 Iterator Abstract Operations

1.1.1.1 IfAbruptCloseAsyncIterator ( value, iteratorRecord )

IfAbruptCloseAsyncIterator is a shorthand for a sequence of algorithm steps that use an Iterator Record. An algorithm step of the form:

  1. IfAbruptCloseAsyncIterator(value, iteratorRecord).

means the same thing as:

  1. If value is an abrupt completion, then
    1. Perform ? AsyncIteratorClose(iteratorRecord, value).
    2. Return value.
  2. Else if value is a Completion Record, set value to value.[[Value]].

1.2 AsyncFunction Objects

1.2.1 Async Functions Abstract Operations

1.2.1.1 AsyncBlockStart ( promiseCapability, asyncBody, asyncContext )

The abstract operation AsyncBlockStart takes arguments promiseCapability (a PromiseCapability Record), asyncBody (a Parse Node or an Abstract Closure with no parameters), and asyncContext (an execution context). It performs the following steps when called:

  1. Assert: promiseCapability is a PromiseCapability Record.
  2. Let runningContext be the running execution context.
  3. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context the following steps will be performed:
    1. If asyncBody is a Parse Node, then
      1. Let result be the result of evaluating asyncBody.
    2. Else,
      1. Assert: asyncBody is an Abstract Closure with no parameters.
      2. Let result be asyncBody().
    3. Let result be the result of evaluating asyncBody.
    4. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done.
    5. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
    6. If result.[[Type]] is normal, then
      1. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »).
    7. Else if result.[[Type]] is return, then
      1. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »).
    8. Else,
      1. Assert: result.[[Type]] is throw.
      2. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »).
    9. Return.
  4. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
  5. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation.
  6. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context.
  7. Assert: result is a normal completion with a value of undefined. The possible sources of completion values are Await or, if the async function doesn't await anything, step 3.i above.
  8. Return.

2 Indexed Collections

2.1 Array Objects

2.1.1 Properties of the Array Constructor

2.1.1.1 Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )

Editor's Note

This section is a wholly new subsection of the original Properties of the Array Constructor clause, to be inserted before the Array.from clause.

When the fromAsync method is called, the following steps are taken:

  1. Let C be the this value.
  2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
  3. Let fromAsyncClosure be a new Abstract Closure with no parameters that captures C, mapfn, and thisArg and performs the following steps when called:
    1. If mapfn is undefined, let mapping be false.
    2. Else,
      1. If IsCallable(mapfn) is false, throw a TypeError exception.
      2. Let mapping be true.
    3. Let usingAsyncIterator be ? GetMethod(asyncItems, @@asyncIterator).
    4. If usingAsyncIterator is undefined, then
      1. Let usingSyncIterator be ? GetMethod(asyncItems, @@iterator).
    5. If IsConstructor(C) is true, then
      1. Let A be ? Construct(C).
    6. Else,
      1. Let A be ! ArrayCreate(0).
    7. Let iteratorRecord be undefined.
    8. If usingAsyncIterator is not undefined, then
      1. Set iteratorRecord to ? GetIterator(asyncItems, async, usingAsyncIterator).
    9. Else if usingSyncIterator is not undefined, then
      1. Set iteratorRecord to ? CreateAsyncFromSyncIterator(GetIterator(asyncItems, sync, usingSyncIterator)).
    10. If iteratorRecord is not undefined, then
      1. Let k be 0.
      2. Repeat,
        1. If k ≥ 253 - 1, then
          1. Let error be ThrowCompletion(a newly created TypeError object).
          2. Return ? AsyncIteratorClose(iteratorRecord, error).
        2. Let Pk be ! ToString(𝔽(k)).
        3. Let next be ? Await(IteratorStep(iteratorRecord)).
        4. If next is false, then
          1. Perform ? Set(A, "length", 𝔽(k), true).
          2. Return Completion Record { [[Type]]: return, [[Value]]: A, [[Target]]: empty }.
        5. Let nextValue be ? IteratorValue(next).
        6. If mapping is true, then
          1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »).
          2. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
          3. Set mappedValue to Await(mappedValue).
          4. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
        7. Else, let mappedValue be nextValue.
        8. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue).
        9. If defineStatus is an abrupt completion, return ? AsyncIteratorClose(iteratorRecord, defineStatus).
        10. Set k to k + 1.
    11. Else,
      1. NOTE: asyncItems is neither an AsyncIterable nor an Iterable so assume it is an array-like object.
      2. Let arrayLike be ! ToObject(asyncItems).
      3. Let len be ? LengthOfArrayLike(arrayLike).
      4. If IsConstructor(C) is true, then
        1. Let A be ? Construct(C, « 𝔽(len) »).
      5. Else,
        1. Let A be ? ArrayCreate(len).
      6. Let k be 0.
      7. Repeat, while k < len,
        1. Let Pk be ! ToString(𝔽(k)).
        2. Let kValue be ? Get(arrayLike, Pk).
        3. Let kValue be ? Await(kValue).
        4. If mapping is true, then
          1. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »).
          2. Let mappedValue be ? Await(mappedValue).
        5. Else, let mappedValue be kValue.
        6. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
        7. Set k to k + 1.
      8. Perform ? Set(A, "length", 𝔽(len), true).
      9. Return Completion Record { [[Type]]: return, [[Value]]: A, [[Target]]: empty }.
  4. Perform AsyncFunctionStart(promiseCapability, fromAsyncClosure).
  5. Return promiseCapability.[[Promise]].
Note

The fromAsync function is an intentionally generic factory method; it does not require that its this value be the Array constructor. Therefore it can be transferred to or inherited by any other constructors that may be called with a single numeric argument.