// 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: --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); %PerformMicrotaskCheckpoint(); 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; }); %PerformMicrotaskCheckpoint(); 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: <>, 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; }); %PerformMicrotaskCheckpoint(); if (testFailed) { throw testFailure; } (function ExtractedAsyncFromSyncIteratorMethods() { // TODO(ishell, caitp): Rewrite the test without using function.caller. // According to ES#sec-built-in-function-objects all built-in functions // must be strict. And ES#sec-forbidden-extensions states that the value of // a function.caller must not be a strict function. return; // 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"); }); %PerformMicrotaskCheckpoint(); 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); })(); (function AsyncFromSyncIteratorOrdering() { let i = 0; let log = []; function r(value, done) { let number = (++i); return { get value() { log.push("get iterResult #" + number + ".value"); return { get then() { log.push("get nextValue#" + number + ".then"); return (r) => { log.push("call nextValue#" + number + ".then"); r(value); } } }; }, get done() { log.push("get iterResult #" + number + ".done"); return done; } }; } var results = [r("value1", false), r("value2", false), r("value3", true), r("value4", false)]; var iter = { get [Symbol.asyncIterator]() { log.push("get syncIterable[@@asyncIterator]"); return null; }, get [Symbol.iterator]() { log.push("get syncIterable[@@iterator]"); return (...args) => { log.push("call syncIterable[@@iterator](" + args.join(", ") + ")"); return this; } }, next_: 0, get next() { log.push("get syncIterable.next"); return (...args) => { let i = this.next_++; log.push("call syncIterable.next(" + args.join(", ") + ")"); return results[i]; } } }; async function iterate(iterable) { log.push("before"); for await (let x of iterable) { log.push("got value " + x); } log.push("after"); return log; } iterate(iter).then(log => { assertEquals([ "before", "get syncIterable[@@asyncIterator]", "get syncIterable[@@iterator]", "call syncIterable[@@iterator]()", "get syncIterable.next", "call syncIterable.next()", "get iterResult #1.done", "get iterResult #1.value", "get nextValue#1.then", "call nextValue#1.then", "got value value1", "call syncIterable.next()", "get iterResult #2.done", "get iterResult #2.value", "get nextValue#2.then", "call nextValue#2.then", "got value value2", "call syncIterable.next()", "get iterResult #3.done", "get iterResult #3.value", "get nextValue#3.then", "call nextValue#3.then", "after" ], log) }).catch(x => %AbortJS(String(x))); })();