Skip to content

Commit

Permalink
add Array.fromAsync proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
zloirock committed Sep 18, 2021
1 parent f73a13f commit 817c4dd
Show file tree
Hide file tree
Showing 22 changed files with 363 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Changelog
##### Unreleased
- Added [`Array.fromAsync` stage 1 proposal](https://github.com/tc39/proposal-array-from-async):
- `Array.fromAsync`
- `%TypedArray%.fromAsync`
- `.name` and `.toString()` on polyfilled functions improved in many different cases
- Improved internal `IsConstructor` and `IsCallable` checks
- Fixed some internal cases of `GetMethod` operation
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Promise.resolve(32).then(x => console.log(x)); // => 32
- [New collections methods](#new-collections-methods)
- [`.of` and `.from` methods on collection constructors](#of-and-from-methods-on-collection-constructors)
- [`compositeKey` and `compositeSymbol`](#compositekey-and-compositesymbol)
- [`Array.fromAsync`](#arrayfromasync)
- [`Array` filtering](#array-filtering)
- [`Array` grouping](#array-grouping)
- [`Array` deduplication](#array-deduplication)
Expand Down Expand Up @@ -2297,6 +2298,27 @@ console.log(compositeSymbol(1, a) === compositeSymbol(1, a)); // => true
console.log(compositeSymbol(1, a, 2, b) === compositeSymbol(1, a, 2, b)); // => true
console.log(compositeSymbol(a, a) === compositeSymbol(a, a)); // => true
```
##### [`Array.fromAsync`](https://github.com/tc39/proposal-array-from-async)[⬆](#index)
Modules [`esnext.array.from-async`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array.from-async.js) and [`esnext.typed-array.from-async`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.typed-array.from-async.js)
```js
class Array {
static fromAsync(asyncItems: AsyncIterable | Iterable | ArrayLike, mapfn?: (value: any, index: number) => any, thisArg?: any): Array;
}

class %TypedArray% {
static fromAsync(asyncItems: AsyncIterable | Iterable | ArrayLike, mapfn?: (value: number, index: number, target) => number, thisArg?: any): %TypedArray%;
}
```
[*CommonJS entry points:*](#commonjs-api)
```js
core-js/proposals/array-from-async
core-js(-pure)/features/array/from-async
core-js/features/typed-array/from-async
```
[*Example*](https://goo.gl/Jt7SsD):
```js
await Array.fromAsync((async function * (){ yield * [1, 2, 3] })(), i => i * i); // => [1, 4, 9]
```
##### [Array filtering](https://github.com/tc39/proposal-array-filtering)[⬆](#index)
Modules [`esnext.array.filter-reject`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array.filter-reject.js) and [`esnext.typed-array.filter-reject`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.typed-array.filter-reject.js).
```js
Expand Down
4 changes: 4 additions & 0 deletions packages/core-js-compat/src/data.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1428,6 +1428,8 @@ export const data = {
},
// TODO: Remove from `core-js@4`
'esnext.aggregate-error': null,
'esnext.array.from-async': {
},
// TODO: Remove from `core-js@4`
'esnext.array.at': null,
// TODO: Remove from `core-js@4`
Expand Down Expand Up @@ -1700,6 +1702,8 @@ export const data = {
// TODO: Remove from `core-js@4`
'esnext.symbol.replace-all': {
},
'esnext.typed-array.from-async': {
},
// TODO: Remove from `core-js@4`
'esnext.typed-array.at': null,
// TODO: Remove from `core-js@4`
Expand Down
4 changes: 4 additions & 0 deletions packages/core-js-compat/src/modules-by-versions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,8 @@ export default {
'es.string.at-alternative',
'es.typed-array.at',
],
3.18: [
'esnext.array.from-async',
'esnext.typed-array.from-async',
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// empty
8 changes: 8 additions & 0 deletions packages/core-js/features/array/from-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require('../../modules/es.array.iterator');
require('../../modules/es.object.to-string');
require('../../modules/es.promise');
require('../../modules/es.string.iterator');
require('../../modules/esnext.array.from-async');
var path = require('../../internals/path');

module.exports = path.Array.fromAsync;
2 changes: 2 additions & 0 deletions packages/core-js/features/array/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
var parent = require('../../stable/array');
require('../../modules/es.map');
require('../../modules/es.promise');
require('../../modules/esnext.array.from-async');
// TODO: Remove from `core-js@4`
require('../../modules/esnext.array.at');
// TODO: Remove from `core-js@4`
Expand Down
1 change: 1 addition & 0 deletions packages/core-js/features/typed-array/from-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('../../modules/esnext.typed-array.from-async');
2 changes: 2 additions & 0 deletions packages/core-js/features/typed-array/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
var parent = require('../../stable/typed-array');
require('../../modules/es.map');
require('../../modules/es.promise');
require('../../modules/esnext.typed-array.from-async');
// TODO: Remove from `core-js@4`
require('../../modules/esnext.typed-array.at');
// TODO: Remove from `core-js@4`
Expand Down
32 changes: 32 additions & 0 deletions packages/core-js/internals/array-from-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';
var bind = require('../internals/function-bind-context');
var toObject = require('../internals/to-object');
var isConstructor = require('../internals/is-constructor');
var getAsyncIterator = require('../internals/get-async-iterator');
var getIterator = require('../internals/get-iterator');
var getIteratorMethod = require('../internals/get-iterator-method');
var getMethod = require('../internals/get-method');
var getVirtual = require('../internals/entry-virtual');
var wellKnownSymbol = require('../internals/well-known-symbol');
var AsyncFromSyncIterator = require('../internals/async-from-sync-iterator');
var toArray = require('../internals/async-iterator-iteration').toArray;

var ASYNC_ITERATOR = wellKnownSymbol('asyncIterator');
var arrayIterator = getVirtual('Array').values;

// `Array.fromAsync` method implementation
// https://github.com/tc39/proposal-array-from-async
module.exports = function fromAsync(asyncItems /* , mapfn = undefined, thisArg = undefined */) {
var O = toObject(asyncItems);
var argumentsLength = arguments.length;
var mapfn = argumentsLength > 1 ? arguments[1] : undefined;
if (mapfn !== undefined) mapfn = bind(mapfn, argumentsLength > 2 ? arguments[2] : undefined, 2);
var usingAsyncIterator = getMethod(O, ASYNC_ITERATOR);
var usingIterator;
if (!usingAsyncIterator) usingIterator = getIteratorMethod(O);
var A = isConstructor(this) ? new this() : [];
var iterator = usingAsyncIterator
? getAsyncIterator(O, usingAsyncIterator)
: new AsyncFromSyncIterator(getIterator(O, usingIterator || arrayIterator));
return toArray(iterator, mapfn, A);
};
31 changes: 21 additions & 10 deletions packages/core-js/internals/async-iterator-iteration.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ var anObject = require('../internals/an-object');
var getBuiltIn = require('../internals/get-built-in');
var getMethod = require('../internals/get-method');

var Promise = getBuiltIn('Promise');
var MAX_SAFE_INTEGER = 9007199254740991;
var push = [].push;

var createMethod = function (TYPE) {
var IS_TO_ARRAY = TYPE == 0;
var IS_FOR_EACH = TYPE == 1;
var IS_EVERY = TYPE == 2;
var IS_SOME = TYPE == 3;
return function (iterator, fn) {
return function (iterator, fn, target) {
anObject(iterator);
var Promise = getBuiltIn('Promise');
var next = aCallable(iterator.next);
var array = IS_TO_ARRAY ? [] : undefined;
if (!IS_TO_ARRAY) aCallable(fn);
var counter = -1;
var MAPPING = fn !== undefined;
if (MAPPING || !IS_TO_ARRAY) aCallable(fn);

return new Promise(function (resolve, reject) {
var closeIteration = function (method, argument) {
Expand All @@ -41,25 +43,34 @@ var createMethod = function (TYPE) {

var loop = function () {
try {
if (IS_TO_ARRAY && (++counter > MAX_SAFE_INTEGER) && MAPPING) {
throw TypeError('The allowed number of iterations has been exceeded');
}
Promise.resolve(anObject(next.call(iterator))).then(function (step) {
try {
if (anObject(step).done) {
resolve(IS_TO_ARRAY ? array : IS_SOME ? false : IS_EVERY || undefined);
if (IS_TO_ARRAY) {
target.length = counter;
resolve(target);
} else resolve(IS_SOME ? false : IS_EVERY || undefined);
} else {
var value = step.value;
if (IS_TO_ARRAY) {
push.call(array, value);
loop();
} else {
Promise.resolve(fn(value)).then(function (result) {
if (MAPPING) {
Promise.resolve(IS_TO_ARRAY ? fn(value, counter) : fn(value)).then(function (result) {
if (IS_FOR_EACH) {
loop();
} else if (IS_EVERY) {
result ? loop() : closeIteration(resolve, false);
} else if (IS_TO_ARRAY) {
push.call(target, result);
loop();
} else {
result ? closeIteration(resolve, IS_SOME || value) : loop();
}
}, onError);
} else {
push.call(target, value);
loop();
}
}
} catch (error) { onError(error); }
Expand Down
8 changes: 8 additions & 0 deletions packages/core-js/modules/esnext.array.from-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var $ = require('../internals/export');
var fromAsync = require('../internals/array-from-async');

// `Array.fromAsync` method
// https://github.com/tc39/proposal-array-from-async
$({ target: 'Array', stat: true }, {
fromAsync: fromAsync
});
2 changes: 1 addition & 1 deletion packages/core-js/modules/esnext.async-iterator.to-array.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ var $toArray = require('../internals/async-iterator-iteration').toArray;

$({ target: 'AsyncIterator', proto: true, real: true }, {
toArray: function toArray() {
return $toArray(this);
return $toArray(this, undefined, []);
}
});
19 changes: 19 additions & 0 deletions packages/core-js/modules/esnext.typed-array.from-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';
var aConstructor = require('../internals/a-constructor');
var arrayFromAsync = require('../internals/array-from-async');
var TYPED_ARRAYS_CONSTRUCTORS_REQUIRES_WRAPPERS = require('../internals/typed-array-constructors-require-wrappers');
var ArrayBufferViewCore = require('../internals/array-buffer-view-core');
var arrayFromConstructorAndList = require('../internals/array-from-constructor-and-list');

var aTypedArrayConstructor = ArrayBufferViewCore.aTypedArrayConstructor;
var exportTypedArrayStaticMethod = ArrayBufferViewCore.exportTypedArrayStaticMethod;

// `%TypedArray%.fromAsync` method
// https://github.com/tc39/proposal-array-from-async
// eslint-disable-next-line -- required for .length
exportTypedArrayStaticMethod('fromAsync', function fromAsync(asyncItems /* , mapfn = undefined, thisArg = undefined */) {
var C = aConstructor(this);
return arrayFromAsync.apply(Array, arguments).then(function (list) {
return arrayFromConstructorAndList(aTypedArrayConstructor(C), list);
});
}, TYPED_ARRAYS_CONSTRUCTORS_REQUIRES_WRAPPERS);
3 changes: 3 additions & 0 deletions packages/core-js/proposals/array-from-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// https://github.com/tc39/proposal-array-from-async
require('../modules/esnext.array.from-async');
require('../modules/esnext.typed-array.from-async');
1 change: 1 addition & 0 deletions packages/core-js/stage/1.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require('../proposals/array-filtering');
require('../proposals/array-from-async');
require('../proposals/array-grouping');
require('../proposals/array-last');
require('../proposals/array-unique');
Expand Down
3 changes: 3 additions & 0 deletions tests/commonjs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
const Set = load(NS, 'set');
const WeakMap = load(NS, 'weak-map');
const WeakSet = load(NS, 'weak-set');
ok(typeof load(NS, 'array/from-async') === 'function');
ok(typeof load(NS, 'array/filter-out') === 'function');
ok(typeof load(NS, 'array/filter-reject') === 'function');
ok(load(NS, 'array/find-last')([1, 2, 3], it => it % 2) === 3);
Expand Down Expand Up @@ -741,6 +742,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
load('proposals/array-last');
load('proposals/array-filtering');
load('proposals/array-find-from-last');
load('proposals/array-from-async');
load('proposals/array-grouping');
load('proposals/array-is-template-object');
load('proposals/array-unique');
Expand Down Expand Up @@ -852,6 +854,7 @@ for (const NS of ['es', 'stable', 'features']) {
{
const NS = 'features';

load(NS, 'typed-array/from-async');
load(NS, 'typed-array/filter-out');
load(NS, 'typed-array/filter-reject');
load(NS, 'typed-array/find-last');
Expand Down
6 changes: 6 additions & 0 deletions tests/compat/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,9 @@ GLOBAL.tests = {
&& set.add({}) == set
&& set[Symbol.toStringTag];
}],
'esnext.array.from-async': function () {
return Array.fromAsync;
},
'esnext.array.filter-reject': function () {
return [].filterReject;
},
Expand Down Expand Up @@ -1470,6 +1473,9 @@ GLOBAL.tests = {
'esnext.symbol.observable': function () {
return Symbol.observable;
},
'esnext.typed-array.from-async': function () {
return Int8Array.fromAsync;
},
'esnext.typed-array.filter-reject': function () {
return Int8Array.prototype.filterReject;
},
Expand Down
24 changes: 24 additions & 0 deletions tests/helpers/helpers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Promise from 'core-js-pure/es/promise';
import ITERATOR from 'core-js-pure/features/symbol/iterator';
import ASYNC_ITERATOR from 'core-js-pure/features/symbol/async-iterator';

export function createIterator(elements, methods) {
let index = 0;
Expand Down Expand Up @@ -40,6 +41,29 @@ export function createIterable(elements, methods) {
return iterable;
}

export function createAsyncIterable(elements, methods) {
const iterable = {
called: false,
received: false,
[ASYNC_ITERATOR]() {
iterable.received = true;
let index = 0;
const iterator = {
next() {
iterable.called = true;
return Promise.resolve({
value: elements[index++],
done: index > elements.length,
});
},
};
if (methods) for (const key in methods) iterator[key] = methods[key];
return iterator;
},
};
return iterable;
}

export function includes(target, wanted) {
for (const element of target) if (wanted === element) return true;
return false;
Expand Down
65 changes: 65 additions & 0 deletions tests/pure/esnext.array.from-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { createAsyncIterable, createIterable } from '../helpers/helpers';
import { STRICT_THIS } from '../helpers/constants';
import Promise from 'core-js-pure/es/promise';
import fromAsync from 'core-js-pure/features/array/from-async';

QUnit.test('Array.fromAsync', assert => {
assert.expect(25);
const async = assert.async();

assert.isFunction(fromAsync);
assert.arity(fromAsync, 1);
assert.name(fromAsync, 'fromAsync');

function C() { /* empty */ }

fromAsync(createAsyncIterable([1, 2, 3]), it => it ** 2).then(it => {
assert.arrayEqual(it, [1, 4, 9], 'async iterable and mapfn');
return fromAsync(createAsyncIterable([1]), function (arg, index) {
assert.same(this, STRICT_THIS, 'this');
assert.same(arguments.length, 2, 'arguments length');
assert.same(arg, 1, 'argument');
assert.same(index, 0, 'index');
});
}).then(() => {
return fromAsync(createAsyncIterable([1, 2, 3]));
}).then(it => {
assert.arrayEqual(it, [1, 2, 3], 'async iterable without mapfn');
return fromAsync(createIterable([1, 2, 3]), arg => arg ** 2);
}).then(it => {
assert.arrayEqual(it, [1, 4, 9], 'iterable and mapfn');
return fromAsync(createIterable([1, 2, 3]), arg => Promise.resolve(arg ** 2));
}).then(it => {
assert.arrayEqual(it, [1, 4, 9], 'iterable and async mapfn');
return fromAsync(createIterable([1]), function (arg, index) {
assert.same(this, STRICT_THIS, 'this');
assert.same(arguments.length, 2, 'arguments length');
assert.same(arg, 1, 'argument');
assert.same(index, 0, 'index');
});
}).then(() => {
return fromAsync(createIterable([1, 2, 3]));
}).then(it => {
assert.arrayEqual(it, [1, 2, 3], 'iterable and without mapfn');
return fromAsync([1, Promise.resolve(2), 3]);
}).then(it => {
assert.arrayEqual(it, [1, 2, 3], 'array');
return fromAsync('123');
}).then(it => {
assert.arrayEqual(it, ['1', '2', '3'], 'string');
return fromAsync.call(C, [1]);
}).then(it => {
assert.ok(it instanceof C, 'subclassable');
return fromAsync({ length: 1, 0: 1 });
}).then(it => {
assert.arrayEqual(it, [1], 'non-iterable');
return fromAsync(createIterable([1]), () => { throw 42; });
}).catch(error => {
assert.same(error, 42, 'rejection on a callback error');
}).then(() => async());

assert.throws(() => fromAsync(undefined, () => { /* empty */ }), TypeError);
assert.throws(() => fromAsync(null, () => { /* empty */ }), TypeError);
assert.throws(() => fromAsync([1], null), TypeError);
assert.throws(() => fromAsync([1], {}), TypeError);
});
Loading

0 comments on commit 817c4dd

Please sign in to comment.