diff --git a/.gitattributes b/.gitattributes index 0d00e318..ee5be791 100644 --- a/.gitattributes +++ b/.gitattributes @@ -371,6 +371,7 @@ 2019/CanonicalNumericIndexString.js spackled linguist-generated=true 2019/CompletePropertyDescriptor.js spackled linguist-generated=true 2019/CopyDataProperties.js spackled linguist-generated=true +2019/CreateAsyncFromSyncIterator.js spackled linguist-generated=true 2019/CreateDataProperty.js spackled linguist-generated=true 2019/CreateDataPropertyOrThrow.js spackled linguist-generated=true 2019/CreateHTML.js spackled linguist-generated=true @@ -482,4 +483,4 @@ 2019/thisBooleanValue.js spackled linguist-generated=true 2019/thisNumberValue.js spackled linguist-generated=true 2019/thisStringValue.js spackled linguist-generated=true -2019/thisSymbolValue.js spackled linguist-generated=true \ No newline at end of file +2019/thisSymbolValue.js spackled linguist-generated=true diff --git a/2018/CreateAsyncFromSyncIterator.js b/2018/CreateAsyncFromSyncIterator.js new file mode 100644 index 00000000..ef74a997 --- /dev/null +++ b/2018/CreateAsyncFromSyncIterator.js @@ -0,0 +1,20 @@ +'use strict'; + +var AsyncFromSyncIterator = require('../helpers/AsyncFromSyncIterator'); +var Get = require('./Get'); + +// https://www.ecma-international.org/ecma-262/9.0/#sec-createasyncfromsynciterator + +module.exports = function CreateAsyncFromSyncIterator(syncIteratorRecord) { + if (!AsyncFromSyncIterator) { + throw new SyntaxError('This environment does not support Promises.'); + } + + var asyncIterator = new AsyncFromSyncIterator(syncIteratorRecord); + var nextMethod = Get(asyncIterator, 'next'); + return { + '[[Iterator]]': asyncIterator, + '[[NextMethod]]': nextMethod, + '[[Done]]': false + }; +}; diff --git a/2019/CreateAsyncFromSyncIterator.js b/2019/CreateAsyncFromSyncIterator.js new file mode 100644 index 00000000..ef74a997 --- /dev/null +++ b/2019/CreateAsyncFromSyncIterator.js @@ -0,0 +1,20 @@ +'use strict'; + +var AsyncFromSyncIterator = require('../helpers/AsyncFromSyncIterator'); +var Get = require('./Get'); + +// https://www.ecma-international.org/ecma-262/9.0/#sec-createasyncfromsynciterator + +module.exports = function CreateAsyncFromSyncIterator(syncIteratorRecord) { + if (!AsyncFromSyncIterator) { + throw new SyntaxError('This environment does not support Promises.'); + } + + var asyncIterator = new AsyncFromSyncIterator(syncIteratorRecord); + var nextMethod = Get(asyncIterator, 'next'); + return { + '[[Iterator]]': asyncIterator, + '[[NextMethod]]': nextMethod, + '[[Done]]': false + }; +}; diff --git a/es2018.js b/es2018.js index 17f978d3..fc2d3fa9 100644 --- a/es2018.js +++ b/es2018.js @@ -14,6 +14,7 @@ var ES2018 = { CanonicalNumericIndexString: require('./2018/CanonicalNumericIndexString'), CompletePropertyDescriptor: require('./2018/CompletePropertyDescriptor'), CopyDataProperties: require('./2018/CopyDataProperties'), + CreateAsyncFromSyncIterator: require('./2018/CreateAsyncFromSyncIterator'), CreateDataProperty: require('./2018/CreateDataProperty'), CreateDataPropertyOrThrow: require('./2018/CreateDataPropertyOrThrow'), CreateHTML: require('./2018/CreateHTML'), diff --git a/es2019.js b/es2019.js index c3dc61f9..966cca43 100644 --- a/es2019.js +++ b/es2019.js @@ -15,6 +15,7 @@ var ES2019 = { CanonicalNumericIndexString: require('./2019/CanonicalNumericIndexString'), CompletePropertyDescriptor: require('./2019/CompletePropertyDescriptor'), CopyDataProperties: require('./2019/CopyDataProperties'), + CreateAsyncFromSyncIterator: require('./2019/CreateAsyncFromSyncIterator'), CreateDataProperty: require('./2019/CreateDataProperty'), CreateDataPropertyOrThrow: require('./2019/CreateDataPropertyOrThrow'), CreateHTML: require('./2019/CreateHTML'), diff --git a/helpers/AsyncFromSyncIterator.js b/helpers/AsyncFromSyncIterator.js new file mode 100644 index 00000000..41a3140c --- /dev/null +++ b/helpers/AsyncFromSyncIterator.js @@ -0,0 +1,230 @@ +'use strict'; + +var internalSlot = require('internal-slot'); +var isCallable = require('is-callable'); + +var callBind = require('./callBind'); +var callBound = require('./callBound'); +var GetIntrinsic = require('../GetIntrinsic'); +var $isNaN = require('./isNaN'); + +var $apply = GetIntrinsic('%Reflect.apply%', true) || callBound('%Function.prototype.apply%'); +var $asyncIterator = GetIntrinsic('%Symbol.asyncIterator%', true); +var $toStringTag = GetIntrinsic('%Symbol.toStringTag%', true); +var $TypeError = GetIntrinsic('%TypeError%'); + +var undefined; + +// TODO: Use %AsyncIterator.from% once it's in ECMA-262 + +/** @type {(o: object, p: string | symbol, Desc: import('es-abstract').PropertyDescriptor) => boolean} */ +var DefineOwnProperty = callBind( + require('./DefineOwnProperty'), + undefined, + function IsDataDescriptor(Desc) { + return !('[[Get]]' in Desc) && !('[[Set]]' in Desc); + }, + GetIntrinsic('%Object.is%', true) || function SameValue(x, y) { + if (x === y) { + // 0 === -0, but they are not identical. + if (x === 0) { + return 1 / x === 1 / y; + } + return true; + } + return $isNaN(x) && $isNaN(y); + }, + function FromPropertyDescriptor(Desc) { + var obj = {}; + if ('[[Value]]' in Desc) { + obj.value = Desc['[[Value]]']; + } + if ('[[Writable]]' in Desc) { + obj.writable = Desc['[[Writable]]']; + } + if ('[[Get]]' in Desc) { + obj.get = Desc['[[Get]]']; + } + if ('[[Set]]' in Desc) { + obj.set = Desc['[[Set]]']; + } + if ('[[Enumerable]]' in Desc) { + obj.enumerable = Desc['[[Enumerable]]']; + } + if ('[[Configurable]]' in Desc) { + obj.configurable = Desc['[[Configurable]]']; + } + return obj; + } +); + +var CreateMethodProperty = function CreateMethodProperty(O, P, V) { + return DefineOwnProperty(O, P, { + '[[Configurable]]': true, + '[[Enumerable]]': false, + '[[Writable]]': true, + '[[Value]]': V + }); +}; + +var isObject = function (target) { + return target !== null && (typeof target === 'object' || typeof target === 'function'); +}; + +var AsyncIteratorPrototype + = GetIntrinsic('%AsyncIteratorPrototype%', true) + || (function () { + var result = {}; + if ($toStringTag) { + DefineOwnProperty(result, $toStringTag, { + '[[Writable]]': false, + '[[Enumerable]]': false, + '[[Configurable]]': true, + '[[Value]]': 'AsyncIterator' + }); + } + if ($asyncIterator) { + CreateMethodProperty( + result, + $asyncIterator, + { + '[Symbol.asyncIterator]': function () { + return this; + } + }['[Symbol.asyncIterator]'] + ); + } + return result; + }()); + +var AsyncFromSyncIteratorPrototype + = GetIntrinsic('%AsyncFromSyncIteratorPrototype%', true) + // eslint-disable-next-line max-lines-per-function + || (function () { + var $Promise = GetIntrinsic('%Promise%', true); + if (!$Promise) { + return; + } + + var PromiseResolve = callBind(GetIntrinsic('%Promise.resolve%')); + var promiseProtoThen = callBound('%Promise.prototype.then%'); + var AsyncFromSyncIteratorContinuation = function AsyncFromSyncIteratorContinuation(result, promiseCapability) { + var done = !!result.done; + var value = result.value; + var valueWrapper = PromiseResolve($Promise, value); + + promiseProtoThen( + valueWrapper, + function onFulfilled(unwrappedValue) { + $apply( + promiseCapability['[[Resolve]]'], + undefined, + [{ value: unwrappedValue, done: done }] + ); + }, + promiseCapability['[[Reject]]'] + ); + }; + + var T = function T() {}; + T.prototype = AsyncIteratorPrototype; + // eslint-disable-next-line no-shadow + var AsyncFromSyncIteratorPrototype = new T(); + + CreateMethodProperty(AsyncFromSyncIteratorPrototype, 'next', function next(value) { + // eslint-disable-next-line no-invalid-this + var O = this; + return new Promise(function (resolve, reject) { + internalSlot.assert(O, '[[SyncIteratorRecord]]'); + + var syncIteratorRecord = internalSlot.get(O, '[[SyncIteratorRecord]]'); + var result = $apply( + syncIteratorRecord['[[NextMethod]]'], + syncIteratorRecord['[[Iterator]]'], + [value] + ); + + if (!isObject(result)) { + throw new $TypeError('iterator next must return an object'); + } + + return AsyncFromSyncIteratorContinuation(result, { + '[[Resolve]]': resolve, + '[[Reject]]': reject + }); + }); + }); + + CreateMethodProperty(AsyncFromSyncIteratorPrototype, 'return', { + 'return': function (value) { + var O = this; + return new Promise(function (resolve, reject) { + internalSlot.assert(O, '[[SyncIteratorRecord]]'); + + var syncIterator = internalSlot.get(O, '[[SyncIteratorRecord]]')['[[Iterator]]']; + var returnMethod = syncIterator['return']; + if (returnMethod != null) { + if (!isCallable(returnMethod)) { + throw new $TypeError('iterator return is not a function'); + } + + return resolve({ + value: value, + done: true + }); + } + + var result = $apply(returnMethod, syncIterator, [value]); + if (!isObject(result)) { + throw new $TypeError('iterator return must return an object'); + } + + return AsyncFromSyncIteratorContinuation(result, { + '[[Resolve]]': resolve, + '[[Reject]]': reject + }); + }); + } + }['return']); + + CreateMethodProperty(AsyncFromSyncIteratorPrototype, 'throw', { + 'throw': function (value) { + var O = this; + return new Promise(function (resolve, reject) { + internalSlot.assert(O, '[[SyncIteratorRecord]]'); + + var syncIterator = internalSlot.get(O, '[[SyncIteratorRecord]]')['[[Iterator]]']; + var throwMethod = syncIterator['return']; + if (throwMethod != null) { + if (!isCallable(throwMethod)) { + throw new $TypeError('iterator throw is not a function'); + } + throw value; + } + + var result = $apply(throwMethod, syncIterator, [value]); + if (!isObject(result)) { + throw new $TypeError('iterator throw must return an object'); + } + + return AsyncFromSyncIteratorContinuation(result, { + '[[Resolve]]': resolve, + '[[Reject]]': reject + }); + }); + } + }['throw']); + + // eslint-disable-next-line consistent-return + return AsyncFromSyncIteratorPrototype; + }()); + +var AsyncFromSyncIteratorConstructor; +if (AsyncFromSyncIteratorPrototype) { + AsyncFromSyncIteratorConstructor = function AsyncFromSyncIterator(syncIteratorRecord) { + internalSlot.set(this, '[[SyncIteratorRecord]]', syncIteratorRecord); + }; + AsyncFromSyncIteratorConstructor.prototype = AsyncFromSyncIteratorPrototype; +} + +module.exports = AsyncFromSyncIteratorConstructor; diff --git a/package.json b/package.json index ecc76781..a7573fa3 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", "is-callable": "^1.2.0", "is-negative-zero": "^2.0.0", "is-regex": "^1.1.0", diff --git a/test/es2018.js b/test/es2018.js index f8c1781e..344f868d 100644 --- a/test/es2018.js +++ b/test/es2018.js @@ -44,7 +44,6 @@ var expectedMissing = [ 'CopyDataBlockBytes', 'CreateArrayFromList', 'CreateArrayIterator', - 'CreateAsyncFromSyncIterator', 'CreateBuiltinFunction', 'CreateByteDataBlock', 'CreateDynamicFunction', diff --git a/test/es2019.js b/test/es2019.js index 4b71dd9e..aad9d625 100644 --- a/test/es2019.js +++ b/test/es2019.js @@ -45,7 +45,6 @@ var expectedMissing = [ 'CopyDataBlockBytes', 'CreateArrayFromList', 'CreateArrayIterator', - 'CreateAsyncFromSyncIterator', 'CreateBuiltinFunction', 'CreateByteDataBlock', 'CreateDynamicFunction',