v8/test/mjsunit/harmony/async-from-sync-iterator.js

768 lines
22 KiB
JavaScript
Raw Normal View History

// 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() {
// 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"); });
%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);
})();
(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");
let i = this.next_++;
return (...args) => {
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",
"get syncIterable.next",
"call syncIterable.next()",
"get iterResult #2.done",
"get iterResult #2.value",
"get nextValue#2.then",
"call nextValue#2.then",
"got value value2",
"get syncIterable.next",
"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)));
})();