Array.fromAsync 方法简介

378次阅读  |  发布于1年以前

Array.fromAsync 方法是 ECMAScript 的一个提案,最近已经进入了 Stage-3 阶段,很可能在明年纳入到 ECMAScript 2023 标准中,本文对 Array.fromAsync 做一个简单介绍。

为什么需要 Array.fromAsync

在 ECMAScript 6 标准中,引入了 Array.from 方法,可以将对一个类似数组或可迭代对象创建一个新的、浅拷贝的数组实例。比如:

// Using an arrow function as the map function to
// manipulate the elements
Array.from([1, 2, 3], x => x + x);
// [2, 4, 6]


// Generate a sequence of numbers
// Since the array is initialized with `undefined` on each position,
// the value of `v` below will be `undefined`
Array.from({length: 5}, (v, i) => i);
// [0, 1, 2, 3, 4]

Array.from 可以看作是一个遍历伪数组对象或可迭代对象并转换成数组的方法:

const arr = [];
for (const v of iterable) {
  arr.push(v);
}

// This does the same thing.
const arr = Array.from(iterable);

但是对于异步迭代对象,还没有类似于 Array.from 的方法,这样的方法对于将整个异步迭代器转储到单个数据结构中也很有用,尤其是在单元测试或命令行场景中:

const arr = [];
for await (const v of asyncIterable) {
  arr.push(v);
}

// We should add something that does the same thing.
const arr = await ??????????(asyncIterable);

正如 Array.from 用于 for 的场景,Array.fromAsync 则是用于 for await 的场景。

Array.fromAsync 的使用方法

与 Array.from 一样,Array.fromAsync 也有三个参数:items, mapfn, thisArg,其中 items 是必填参数,mapfn, thisArg 是选填参数。items 既可以是异步迭代器,也可以是同步迭代器,或者是 array-like 对象。

异步迭代器

Array.fromAsync 可以将一个异步迭代器转化成为一个 promise,并且 resolve 出一个新的数组。在 promise resolve 之前,会根据输入的参数创建一个异步迭代器,然后惰性的迭代它,并且把每次 yield 的返回值添加到数组中。

async function * asyncGen (n) {
  for (let i = 0; i < n; i++)
    yield i * 2;
}

// `arr` will be `[0, 2, 4, 6]`.
const arr = [];
for await (const v of asyncGen(4)) {
  arr.push(v);
}

// This is equivalent.
const arr = await Array.fromAsync(asyncGen(4));

同步迭代器

如果输入的参数是一个同步迭代器,那么 Array.fromAsync 的返回值仍然是一个 promise,然后 resolve 出一个新的数组。如果同步迭代器 yield 的返回值是 promise,那么将 await 每个 promise 的处理结果,并将这些结果添加到新数组中。

function * genPromises (n) {
  for (let i = 0; i < n; i++)
    yield Promise.resolve(i * 2);
}

// `arr` will be `[ 0, 2, 4, 6 ]`.
const arr = [];
for await (const v of genPromises(4)) {
  arr.push(v);
}

// This is equivalent.
const arr = await Array.fromAsync(genPromises(4));

array-like 对象

对于那些有 length 属性和索引属性的 array-like 对象,Array.fromAsync 也会返回一个新数组:

const arrLike = {
  length: 4,
  0: Promise.resolve(0),
  1: Promise.resolve(2),
  2: Promise.resolve(4),
  3: Promise.resolve(6),
}

// `arr` will be `[ 0, 2, 4, 6 ]`.
const arr = [];
for await (const v of Array.from(arrLike)) {
  arr.push(v);
}

// This is equivalent.
const arr = await Array.fromAsync(arrLike);

现实中的使用场景

在下面的测试用例中,需要将 Pipeline 中的数据放到一个数组中,然后完成用例的断言:

async function toArray(items) {
  const result = [];
  for await (const item of items) {
    result.push(item);
  }
  return result;
}

it('empty-pipeline', async () => {
  const pipeline = new Pipeline();
  const result = await toArray(
    pipeline.execute(
      [ 1, 2, 3, 4, 5 ]));
  assert.deepStrictEqual(
    result,
    [ 1, 2, 3, 4, 5 ],
  );
});

使用了 Array.fromAsync 可以简化为:

it('empty-pipeline', async () => {
  const pipeline = new Pipeline();
  const result = await Array.fromAsync(
    pipeline.execute(
      [ 1, 2, 3, 4, 5 ]));
  assert.deepStrictEqual(
    result,
    [ 1, 2, 3, 4, 5 ],
  );
});

polyfill 实现

目前,Array.fromAsync 处于提案阶段,还并未正式成为标准。如果想使用它,需要使用 polyfill 支持,下面是它的示例代码:

async function fromAsync (items, mapfn, thisArg) {
  const itemsAreIterable = (
    asyncIteratorSymbol in items ||
    iteratorSymbol in items
  );

  if (itemsAreIterable) {
    const result = isConstructor(this)
      ? new this
      : IntrinsicArray(0);

    let i = 0;

    for await (const v of items) {
      if (i > MAX_SAFE_INTEGER) {
        throw TypeError(tooLongErrorMessage);
      }

      else if (mapfn) {
        result[i] = await mapfn.call(thisArg, v, i);
      }

      else {
        result[i] = v;
      }

      i ++;
    }

    result.length = i;
    return result;
  }

  else {
    // In this case, the items are assumed to be an arraylike object with
    // a length property and integer properties for each element.
    const { length } = items;
    const result = isConstructor(this)
      ? new this(length)
      : IntrinsicArray(length);

    let i = 0;

    while (i < length) {
      if (i > MAX_SAFE_INTEGER) {
        throw TypeError(tooLongErrorMessage);
      }

      const v = await items[i];

      if (mapfn) {
        result[i] = await mapfn.call(thisArg, v, i);
      }

      else {
        result[i] = v;
      }

      i ++;
    }

    result.length = i;
    return result;
  }
};

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8