Index: test/mjsunit/harmony/async-from-sync-iterator.js |
diff --git a/test/mjsunit/harmony/async-from-sync-iterator.js b/test/mjsunit/harmony/async-from-sync-iterator.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..9ca33c995d8baa33b6925373443e959d27ce56f6 |
--- /dev/null |
+++ b/test/mjsunit/harmony/async-from-sync-iterator.js |
@@ -0,0 +1,670 @@ |
+// Copyright 2017 the V8 project authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+// Flags: --harmony-async-iteration --allow-natives-syntax |
+ |
+let testFailed = false; |
+let testFailure; |
+ |
+function assertThrowsAsync(run, errorType, message) { |
+ var actual; |
+ var hadValue = false; |
+ var hadError = false; |
+ var promise = run(); |
+ |
+ if (typeof promise !== "object" || typeof promise.then !== "function") { |
+ throw new MjsUnitAssertionError( |
+ "Expected " + run.toString() + |
+ " to return a Promise, but it returned " + PrettyPrint(promise)); |
+ } |
+ |
+ promise.then(function(value) { hadValue = true; actual = value; }, |
+ function(error) { hadError = true; actual = error; }); |
+ |
+ assertFalse(hadValue || hadError); |
+ |
+ %RunMicrotasks(); |
+ |
+ if (!hadError) { |
+ throw new MjsUnitAssertionError( |
+ "Expected " + run + "() to throw " + errorType.name + |
+ ", but did not throw."); |
+ } |
+ if (!(actual instanceof errorType)) |
+ throw new MjsUnitAssertionError( |
+ "Expected " + run + "() to throw " + errorType.name + |
+ ", but threw '" + actual + "'"); |
+ if (message !== void 0 && actual.message !== message) |
+ throw new MjsUnitAssertionError( |
+ "Expected " + run + "() to throw '" + message + "', but threw '" + |
+ actual.message + "'"); |
+}; |
+ |
+function resolveLater(value) { |
+ return new Promise(function(resolve) { |
+ Promise.resolve().then(function() { |
+ resolve(value); |
+ }); |
+ }); |
+} |
+ |
+function rejectLater(value) { |
+ return new Promise(function(resolve, reject) { |
+ Promise.resolve().then(function() { |
+ reject(value); |
+ }); |
+ }); |
+} |
+ |
+const kNext = 1; |
+const kThrow = 2; |
+const kReturn = 4; |
+const kNextThrows = kNext | 8; |
+const kReturnThrows = kReturn | 16; |
+const kThrowNormal = kThrow | 32; |
+const kNextUnchanged = kNext | 64; |
+const kReturnUnchanged = kReturn | 128; |
+const kThrowUnchanged = kThrow | 256; |
+function sync(array, features, log) { |
+ // `log` is a required parameter |
+ if (log === void 0) %AbortJS("`log` is undefined"); |
+ |
+ let i = 0; |
+ let methods = { |
+ next(sent) { |
+ let done = i >= array.length; |
+ let value = array[i]; |
+ log.push({ method: "next", sent, value, done }); |
+ if ((features & kNextThrows) === kNextThrows) throw sent; |
+ if ((features & kNextUnchanged) === kNextUnchanged) return sent; |
+ i++; |
+ return { value, done }; |
+ }, |
+ throw(sent) { |
+ let done = i >= array.length; |
+ log.push({ method: "throw", sent, done }); |
+ if ((features & kThrowNormal) === kThrowNormal) |
+ return { value: sent, done }; |
+ if ((features & kThrowUnchanged) === kThrowUnchanged) return sent; |
+ throw sent; |
+ }, |
+ return(sent) { |
+ let done = true; |
+ log.push({ method: "return", sent, done }); |
+ if ((features & kReturnThrows) === kReturnThrows) throw sent; |
+ if ((features & kReturnUnchanged) === kReturnUnchanged) return sent; |
+ return { value: sent, done }; |
+ } |
+ }; |
+ return { |
+ [Symbol.iterator]() { return this; }, |
+ next: (features & kNext) ? methods.next : undefined, |
+ throw: (features & kThrow) ? methods.throw : undefined, |
+ return: (features & kReturn) ? methods.return : undefined |
+ }; |
+} |
+ |
+class MyError extends Error {}; |
+ |
+(async function AsyncFromSyncWithGenerator() { |
+ function* gen() { |
+ yield "sync value"; |
+ try { |
+ yield new Promise(function(resolve) { |
+ resolve("async value"); |
+ }); |
+ } catch (error) { |
+ throw error; |
+ } |
+ assertUnreachable("generator is closed"); |
+ } |
+ let iter = %CreateAsyncFromSyncIterator(gen()); |
+ |
+ // [Async-from-Sync Iterator] wraps sync iterator values in a Promise |
+ let promise = iter.next(); |
+ assertInstanceof(promise, Promise); |
+ let iter_result = await promise; |
+ assertEquals({ value: "sync value", done: false }, iter_result); |
+ |
+ // [Async-from-Sync Iterator] will wait for resolution of Promise values |
+ promise = iter.next(); |
+ assertInstanceof(promise, Promise); |
+ iter_result = await promise; |
+ assertEquals({ value: "async value", done: false }, iter_result); |
+ |
+ // [Async-from-Sync Iterator].throw delegates to .throw() method of sync |
+ // iterator. |
+ promise = iter.throw(new MyError("Error#1")); |
+ assertInstanceof(promise, Promise); |
+ try { |
+ await promise; |
+ assertUnreachable("promise should be rejected"); |
+ } catch (e) { |
+ // If assertUnreachable failed, rethrow |
+ if (e instanceof MjsUnitAssertionError) throw e; |
+ assertInstanceof(e, MyError); |
+ assertEquals("Error#1", e.message); |
+ } |
+ |
+ // Generator is closed, subsequent calls to .next() will not resume. |
+ promise = iter.next("floof"); |
+ iter_result = await promise; |
+ assertEquals({ value: undefined, done: true }, iter_result); |
+ |
+ promise = iter.return("generator closed"); |
+ assertInstanceof(promise, Promise); |
+ iter_result = await promise; |
+ assertEquals({ value: "generator closed", done: true }, iter_result); |
+ |
+ // .next(), .return() and .throw() delegate to sync iterator methods, without |
+ // keeping track of the state of the generator. |
+ promise = iter.next("unused"); |
+ assertInstanceof(promise, Promise); |
+ iter_result = await promise; |
+ assertEquals({ value: undefined, done: true }, iter_result); |
+ |
+ promise = iter.throw(new MyError("Error#2")); |
+ assertInstanceof(promise, Promise); |
+ try { |
+ await promise; |
+ assertUnreachable("promise should be rejected"); |
+ } catch (e) { |
+ // If assertUnreachable failed, rethrow |
+ if (e instanceof MjsUnitAssertionError) throw e; |
+ assertInstanceof(e, MyError); |
+ assertEquals("Error#2", e.message); |
+ } |
+ |
+ promise = iter.return("return-after-completed"); |
+ assertInstanceof(promise, Promise); |
+ iter_result = await promise; |
+ assertEquals({ value: "return-after-completed", done: true }, iter_result); |
+})().catch(function(error) { |
+ testFailed = true; |
+ testFailure = error; |
+}); |
+ |
+%RunMicrotasks(); |
+if (testFailed) { |
+ throw testFailure; |
+} |
+ |
+ |
+(async function AsyncFromSyncOrderOfOperations() { |
+ let log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(["sync-value"], 0, log)); |
+ |
+ try { |
+ await iter.next(); |
+ assertUnreachable("Iterator.next() method is not optional"); |
+ } catch (e) { |
+ assertInstanceof(e, TypeError); |
+ assertEquals([], log); |
+ } |
+ |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(["sync-value"], kNext, log)); |
+ assertEquals({ value: "sync-value", done: false }, await iter.next("a")); |
+ assertEquals([ |
+ { |
+ method: "next", |
+ sent: "a", |
+ value: "sync-value", |
+ done: false |
+ } |
+ ], log); |
+ |
+ log = []; |
+ let asyncValue = resolveLater("async-value"); |
+ iter = %CreateAsyncFromSyncIterator(sync([asyncValue], kNext, log)); |
+ assertEquals({ value: "async-value", done: false }, await iter.next("b")); |
+ assertEquals([ |
+ { |
+ method: "next", |
+ sent: "b", |
+ value: asyncValue, |
+ done: false |
+ } |
+ ], log); |
+ |
+ // If [sync_iterator].next() produces a rejected Promise or an exception is |
+ // thrown, Promise is rejected with thrown/rejected value. |
+ log = []; |
+ asyncValue = rejectLater("Boo!"); |
+ iter = %CreateAsyncFromSyncIterator(sync([asyncValue], kNext, log)); |
+ try { |
+ await iter.next('c'); |
+ assertUnreachable('Expected `iter.next(\'c\') to throw, but did not throw'); |
+ } catch (e) { |
+ assertEquals("Boo!", e); |
+ assertEquals([ |
+ { |
+ method: 'next', |
+ sent: 'c', |
+ value: asyncValue, |
+ done: false |
+ } |
+ ], log); |
+ } |
+ |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNextThrows, log)); |
+ try { |
+ await iter.next('Boo!'); |
+ assertUnreachable('Expected `iter.next(\'c\') to throw, but did not throw'); |
+ } catch (e) { |
+ assertEquals("Boo!", e); |
+ assertEquals([ |
+ { |
+ method: 'next', |
+ sent: 'Boo!', |
+ value: 'sync-value', |
+ done: false |
+ } |
+ ], log); |
+ } |
+ |
+ |
+ // [Async-from-Sync Iterator].next() will be rejected with a TypeError if |
+ // Type([sync_iterator].next()) is not Object. |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNextUnchanged, |
+ log)); |
+ try { |
+ await iter.next('not-a-JSReceiver'); |
+ assertUnreachable('Expected `iter.next(\'not-a-JSReceiver\')` to ' + |
+ 'throw, but did not throw') |
+ } catch (e) { |
+ assertEquals(e.constructor, TypeError); |
+ } |
+ |
+ assertEquals([ |
+ { |
+ method: 'next', |
+ sent: 'not-a-JSReceiver', |
+ value: 'sync-value', |
+ done: false |
+ } |
+ ], log); |
+ |
+ // If [sync_iterator] does not have a .return() method, return a Promise |
+ // resolved with the value `{ value: <<sent value>>, done: true }`. |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-return'], kNext, log)); |
+ assertEquals({ |
+ value: 'd', |
+ done: true |
+ }, await iter.return('d')); |
+ |
+ // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
+ // whether [sync_iterator] is completed or not. |
+ assertEquals({ |
+ value: 'sync-return', |
+ done: false |
+ }, await iter.next('e')); |
+ |
+ assertEquals([ |
+ { |
+ method: 'next', |
+ sent: 'e', |
+ value: 'sync-return', |
+ done: false |
+ } |
+ ], log); |
+ |
+ // If [sync_iterator] does have a .return() method, return a Promise |
+ // fulfilled with the iterator result of [sync_iterator].return(). |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-return'], |
+ kNext|kReturn, log)); |
+ assertEquals({ |
+ value: 'f', |
+ done: true |
+ }, await iter.return('f')); |
+ |
+ // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
+ // whether [sync_iterator] is completed or not. |
+ assertEquals({ |
+ value: 'sync-return', |
+ done: false |
+ }, await iter.next('g')); |
+ |
+ assertEquals([ |
+ { |
+ method: 'return', |
+ sent: 'f', |
+ done: true |
+ }, |
+ { |
+ method: 'next', |
+ sent: 'g', |
+ value: 'sync-return', |
+ done: false |
+ } |
+ ], log); |
+ |
+ // If [sync_iterator].return() produces a rejected Promise or an exception is |
+ // thrown, Promise is rejected with thrown/rejected value. |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kReturnThrows, |
+ log)); |
+ try { |
+ await iter.return('Boo!!'); |
+ assertUnreachable('Expected `iter.return(\'Boo!!\')` to throw, but did ' + |
+ 'not throw'); |
+ } catch (e) { |
+ assertEquals("Boo!!", e); |
+ } |
+ |
+ // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
+ // whether [sync_iterator] is completed or not. |
+ assertEquals({ value: 'sync-value', done: false }, await iter.next('h')); |
+ assertEquals([ |
+ { |
+ method: 'return', |
+ sent: 'Boo!!', |
+ done: true |
+ }, |
+ { |
+ method: 'next', |
+ sent: 'h', |
+ value: 'sync-value', |
+ done: false |
+ } |
+ ], log); |
+ |
+ |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kReturn, log)); |
+ |
+ let rejection = Promise.reject('Boo!!'); |
+ try { |
+ await iter.return(rejection); |
+ assertUnreachable('Expected `iter.return(Promise.reject(\'Boo!!\'))` to ' + |
+ 'throw, but did not throw'); |
+ } catch (e) { |
+ assertEquals('Boo!!', e); |
+ } |
+ |
+ // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
+ // whether [sync_iterator] is completed or not. |
+ assertEquals({ value: 'sync-value', done: false }, await iter.next('i')); |
+ assertEquals([ |
+ { |
+ method: 'return', |
+ sent: rejection, |
+ done: true |
+ }, |
+ { |
+ method: 'next', |
+ sent: 'i', |
+ value: 'sync-value', |
+ done: false |
+ } |
+ ], log); |
+ |
+ // [Async-from-Sync Iterator].return() will be rejected with a TypeError if |
+ // Type([sync_iterator].return()) is not Object. |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], |
+ kNext|kReturnUnchanged, log)); |
+ try { |
+ await iter.return('not-a-JSReceiver'); |
+ assertUnreachable('Expected `iter.return(\'not-a-JSReceiver\')` to ' + |
+ 'throw, but did not throw') |
+ } catch (e) { |
+ assertEquals(e.constructor, TypeError); |
+ } |
+ |
+ // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
+ // whether [sync_iterator] is completed or not. |
+ assertEquals({ value: 'sync-value', done: false }, await iter.next('j')); |
+ assertEquals([ |
+ { |
+ method: 'return', |
+ sent: 'not-a-JSReceiver', |
+ done: true |
+ }, |
+ { |
+ method: 'next', |
+ sent: 'j', |
+ value: 'sync-value', |
+ done: false |
+ } |
+ ], log); |
+ |
+ // If [sync_iterator] does not have a .throw method, return a Promise rejected |
+ // with the sent value. |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext, log)); |
+ try { |
+ await iter.throw('Boo!!'); |
+ assertUnreachable('Expected iter.throw(\'Boo!!\') to throw, but did not ' + |
+ 'throw'); |
+ } catch (e) { |
+ assertEquals('Boo!!', e); |
+ } |
+ |
+ // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
+ // whether [sync_iterator] is completed or not. |
+ assertEquals({ value: 'sync-value', done: false }, await iter.next('k')); |
+ assertEquals([ |
+ { |
+ method: 'next', |
+ sent: 'k', |
+ value: 'sync-value', |
+ done: false |
+ } |
+ ], log); |
+ |
+ |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kThrow, log)); |
+ try { |
+ await iter.throw('Boo!!'); |
+ assertUnreachable('Expected iter.throw(\'Boo!!\') to throw, but did not ' + |
+ 'throw'); |
+ } catch (e) { |
+ assertEquals('Boo!!', e); |
+ } |
+ |
+ // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
+ // whether [sync_iterator] is completed or not. |
+ assertEquals({ value: 'sync-value', done: false }, await iter.next('l')); |
+ assertEquals([ |
+ { |
+ method: 'throw', |
+ sent: 'Boo!!', |
+ done: false |
+ }, |
+ { |
+ method: 'next', |
+ sent: 'l', |
+ value: 'sync-value', |
+ done: false |
+ } |
+ ], log); |
+ |
+ // If [sync_iterator].throw() returns a resolved Promise or a Completion |
+ // with [[Type]] "normal" or "return", return a resolved Promise |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], kNext|kThrowNormal, |
+ log)); |
+ assertEquals({ |
+ value: 'Boo!!', |
+ done: false |
+ }, await iter.throw('Boo!!')); |
+ |
+ // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
+ // whether [sync_iterator] is completed or not. |
+ assertEquals({ value: 'sync-value', done: false }, await iter.next('m')); |
+ assertEquals([ |
+ { |
+ method: 'throw', |
+ sent: 'Boo!!', |
+ done: false |
+ }, |
+ { |
+ method: 'next', |
+ sent: 'm', |
+ value: 'sync-value', |
+ done: false |
+ } |
+ ], log); |
+ |
+ // [Async-from-Sync Iterator].throw() will be rejected with a TypeError if |
+ // Type([sync_iterator].throw()) is not Object. |
+ log = []; |
+ iter = %CreateAsyncFromSyncIterator(sync(['sync-value'], |
+ kNext|kThrowUnchanged, log)); |
+ try { |
+ await iter.throw('not-a-JSReceiver'); |
+ assertUnreachable('Expected `iter.throw(\'not-a-JSReceiver\')` to ' + |
+ 'throw, but did not throw') |
+ } catch (e) { |
+ assertEquals(e.constructor, TypeError); |
+ } |
+ |
+ // [Async-from-Sync Iterator] merely delegates, and does not keep track of |
+ // whether [sync_iterator] is completed or not. |
+ assertEquals({ value: 'sync-value', done: false }, await iter.next('n')); |
+ assertEquals([ |
+ { |
+ method: 'throw', |
+ sent: 'not-a-JSReceiver', |
+ done: false |
+ }, |
+ { |
+ method: 'next', |
+ sent: 'n', |
+ value: 'sync-value', |
+ done: false |
+ } |
+ ], log); |
+ |
+ // Let nextValue be IteratorValue(nextResult). |
+ // IfAbruptRejectPromise(nextValue, promiseCapability).) |
+ iter = %CreateAsyncFromSyncIterator({ |
+ next() { return { get value() { throw "BadValue!" }, done: false }; } |
+ }); |
+ try { |
+ await iter.next(); |
+ assertUnreachable('Expected `iter.next()` to throw, but did not throw'); |
+ } catch (e) { |
+ assertEquals('BadValue!', e); |
+ } |
+ |
+ // Let nextDone be IteratorComplete(nextResult). |
+ // IfAbruptRejectPromise(nextDone, promiseCapability). |
+ iter = %CreateAsyncFromSyncIterator({ |
+ next() { return { value: undefined, get done() { throw "BadValue!" } }; } |
+ }); |
+ try { |
+ await iter.next(); |
+ assertUnreachable('Expected `iter.next()` to throw, but did not throw'); |
+ } catch (e) { |
+ assertEquals('BadValue!', e); |
+ } |
+ |
+ // IfAbruptRejectPromise(returnResult, promiseCapability). |
+ // Let returnValue be IteratorValue(returnResult). |
+ iter = %CreateAsyncFromSyncIterator({ |
+ return() { return { get value() { throw "BadValue!" }, done: false }; } |
+ }); |
+ try { |
+ await iter.return(); |
+ assertUnreachable('Expected `iter.return()` to throw, but did not throw'); |
+ } catch (e) { |
+ assertEquals('BadValue!', e); |
+ } |
+ |
+ // IfAbruptRejectPromise(returnValue, promiseCapability). |
+ // Let returnDone be IteratorComplete(returnResult). |
+ iter = %CreateAsyncFromSyncIterator({ |
+ return() { return { value: undefined, get done() { throw "BadValue!" } }; } |
+ }); |
+ try { |
+ await iter.return(); |
+ assertUnreachable('Expected `iter.return()` to throw, but did not throw'); |
+ } catch (e) { |
+ assertEquals('BadValue!', e); |
+ } |
+ |
+ // IfAbruptRejectPromise(throwResult, promiseCapability). |
+ // Let throwValue be IteratorValue(throwResult). |
+ iter = %CreateAsyncFromSyncIterator({ |
+ throw() { return { get value() { throw "BadValue!" }, done: false }; } |
+ }); |
+ try { |
+ await iter.throw(); |
+ assertUnreachable('Expected `iter.throw()` to throw, but did not throw'); |
+ } catch (e) { |
+ assertEquals('BadValue!', e); |
+ } |
+ |
+ // IfAbruptRejectPromise(throwValue, promiseCapability). |
+ // Let throwDone be IteratorComplete(throwResult). |
+ iter = %CreateAsyncFromSyncIterator({ |
+ throw() { return { value: undefined, get done() { throw "BadValue!" } }; } |
+ }); |
+ try { |
+ await iter.throw(); |
+ assertUnreachable('Expected `iter.throw()` to throw, but did not throw'); |
+ } catch (e) { |
+ assertEquals('BadValue!', e); |
+ } |
+})().catch(function(error) { |
+ testFailed = true; |
+ testFailure = error; |
+}); |
+ |
+%RunMicrotasks(); |
+if (testFailed) { |
+ throw testFailure; |
+} |
+ |
+(function ExtractedAsyncFromSyncIteratorMethods() { |
+ // Async-from-Sync iterator methods can be extracted via function.caller. |
+ // TODO(caitp): test extracted `throw` method using yield* in async generator. |
+ let extractor = [0, 1, 2, 3, 4,5,6,7,8,9]; |
+ let extractedNext; |
+ let extractedReturn; |
+ |
+ extractor[Symbol.iterator] = function() { |
+ let it = [][Symbol.iterator].call(extractor); |
+ let origNext = it.next, origThrow = it.throw, origReturn = it.return; |
+ function extractNext() { |
+ extractedNext = extractNext.caller; |
+ return origNext; |
+ } |
+ function extractReturn() { |
+ extractedReturn = extractReturn.caller; |
+ return origReturn; |
+ } |
+ Object.defineProperties(it, { |
+ "next": { get: extractNext, configurable: true }, |
+ "return": { get: extractReturn, configurable: true } |
+ }); |
+ return it; |
+ }; |
+ |
+ async function f() { |
+ let i; |
+ let it = extractor[Symbol.iterator](); |
+ for await (let x of it) break; |
+ for await (let x of it) return "x"; |
+ } |
+ |
+ // Cycle through `f` to extract iterator methods |
+ f().catch(function() { %AbortJS("No error should have occurred"); }); |
+ %RunMicrotasks(); |
+ |
+ assertEquals(typeof extractedNext, "function"); |
+ assertThrowsAsync(() => extractedNext.call(undefined), TypeError); |
+ assertThrowsAsync(() => extractedNext.call(1), TypeError); |
+ |
+ assertEquals(typeof extractedReturn, "function"); |
+ assertThrowsAsync(() => extractedReturn.call(undefined), TypeError); |
+ assertThrowsAsync(() => extractedReturn.call(1), TypeError); |
+})(); |