mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 07:57:46 +00:00
LibJS/Bytecode: Implement async generators
This commit is contained in:
parent
d4e30710e7
commit
d1cb78c411
9 changed files with 1290 additions and 71 deletions
|
@ -0,0 +1,145 @@
|
|||
describe("correct behaviour", () => {
|
||||
async function* generatorFunction() {
|
||||
yield 1;
|
||||
await Promise.resolve(2);
|
||||
const b = yield 3;
|
||||
await Promise.resolve(b);
|
||||
yield b + 1;
|
||||
yield Promise.resolve(b + 2);
|
||||
yield* [Promise.resolve(b + 3), Promise.resolve(b + 4), Promise.resolve(b + 5)];
|
||||
return b + 6;
|
||||
}
|
||||
|
||||
test("length is 1", () => {
|
||||
expect(generatorFunction.prototype.next).toHaveLength(1);
|
||||
});
|
||||
|
||||
const generator = generatorFunction();
|
||||
|
||||
function runGenerator(valueToPass, unwrapIteratorResult = true) {
|
||||
let result = null;
|
||||
test(`generator runs valueToPass=${valueToPass}`, () => {
|
||||
const promise = generator.next(valueToPass);
|
||||
promise
|
||||
.then(value => {
|
||||
result = value;
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
expect(result).toBeInstanceOf(Object);
|
||||
expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
|
||||
expect(Object.keys(result)).toEqual(["value", "done"]);
|
||||
});
|
||||
return unwrapIteratorResult ? result.value : result;
|
||||
}
|
||||
|
||||
test("can yield", () => {
|
||||
const firstRunResult = runGenerator("bad1");
|
||||
expect(firstRunResult).toBe(1);
|
||||
});
|
||||
|
||||
test("await does not yield", () => {
|
||||
const secondRunResult = runGenerator("bad2");
|
||||
expect(secondRunResult).toBe(3);
|
||||
});
|
||||
|
||||
test("can pass values via yield", () => {
|
||||
const thirdRunResult = runGenerator(4);
|
||||
expect(thirdRunResult).toBe(5);
|
||||
});
|
||||
|
||||
test("yield implicitly awaits", () => {
|
||||
const fourthRunResult = runGenerator("bad3");
|
||||
expect(fourthRunResult).toBe(6);
|
||||
|
||||
const fifthRunResult = runGenerator("bad4");
|
||||
expect(fifthRunResult).toBe(7);
|
||||
|
||||
const sixthRunResult = runGenerator("bad5");
|
||||
expect(sixthRunResult).toBe(8);
|
||||
|
||||
const seventhRunResult = runGenerator("bad6");
|
||||
expect(seventhRunResult).toBe(9);
|
||||
});
|
||||
|
||||
test("can return a value", () => {
|
||||
const eighthRunResult = runGenerator("bad7", false);
|
||||
expect(eighthRunResult.value).toBe(10);
|
||||
expect(eighthRunResult.done).toBeTrue();
|
||||
});
|
||||
|
||||
test("gets undefined in completed state", () => {
|
||||
const ninethRunResult = runGenerator("bad8", false);
|
||||
expect(ninethRunResult.value).toBeUndefined();
|
||||
expect(ninethRunResult.done).toBeTrue();
|
||||
});
|
||||
|
||||
async function* implicitReturnFunction() {
|
||||
0xbbadbeef;
|
||||
}
|
||||
|
||||
const implicitReturnGenerator = implicitReturnFunction();
|
||||
|
||||
test("gets undefined from implicit return", () => {
|
||||
implicitReturnGenerator
|
||||
.next("bad9")
|
||||
.then(iteratorResult => {
|
||||
expect(iteratorResult.value).toBeUndefined();
|
||||
expect(iteratorResult.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Implicit await generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
async function* unhandledExceptionFunction() {
|
||||
throw 1337;
|
||||
}
|
||||
|
||||
const unhandledExceptionGenerator = unhandledExceptionFunction();
|
||||
|
||||
test("promise is rejected on unhandled exceptions", () => {
|
||||
unhandledExceptionGenerator
|
||||
.next("bad10")
|
||||
.then(() => {
|
||||
expect().fail(
|
||||
"Unhandled exception generator did NOT throw an unhandled exception."
|
||||
);
|
||||
})
|
||||
.catch(e => {
|
||||
expect(e).toBe(1337);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
test("generator is complete after unhandled exception", () => {
|
||||
unhandledExceptionGenerator
|
||||
.next("bad11")
|
||||
.then(iteratorResult => {
|
||||
expect(iteratorResult.value).toBeUndefined();
|
||||
expect(iteratorResult.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(
|
||||
"Unhandled exception generator threw an unhandled exception in Completed state."
|
||||
);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be an AsyncGenerator object", () => {
|
||||
async function* generator() {}
|
||||
let rejection = null;
|
||||
generator.prototype.next.call("foo").catch(error => {
|
||||
rejection = error;
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
expect(rejection).toBeInstanceOf(TypeError);
|
||||
expect(rejection.message).toBe("Not an object of type AsyncGenerator");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,146 @@
|
|||
describe("correct behavior", () => {
|
||||
async function* emptyGeneratorFunction() {}
|
||||
|
||||
test("length is 1", () => {
|
||||
expect(emptyGeneratorFunction.prototype.return).toHaveLength(1);
|
||||
});
|
||||
|
||||
const emptyGenerator = emptyGeneratorFunction();
|
||||
|
||||
test("return from SuspendedStart", () => {
|
||||
emptyGenerator
|
||||
.return(1337)
|
||||
.then(result => {
|
||||
expect(result.value).toBe(1337);
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
test("return from Completed", () => {
|
||||
emptyGenerator
|
||||
.return(123)
|
||||
.then(result => {
|
||||
expect(result.value).toBe(123);
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
async function* generatorTwo() {
|
||||
yield 1337;
|
||||
yield 123;
|
||||
}
|
||||
|
||||
const generatorTwoIterator = generatorTwo();
|
||||
|
||||
test("return from SuspendedYield", () => {
|
||||
generatorTwoIterator
|
||||
.next("bad1")
|
||||
.then(result => {
|
||||
expect(result.value).toBe(1337);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
generatorTwoIterator
|
||||
.return(999)
|
||||
.then(result => {
|
||||
expect(result.value).toBe(999);
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
generatorTwoIterator
|
||||
.next("bad2")
|
||||
.then(result => {
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
async function* injectedCompletionGenerator() {
|
||||
try {
|
||||
yield 1;
|
||||
} finally {
|
||||
yield 2;
|
||||
}
|
||||
}
|
||||
|
||||
const injectedCompletionGeneratorObject = injectedCompletionGenerator();
|
||||
|
||||
test("return completion is injected into generator", () => {
|
||||
injectedCompletionGeneratorObject
|
||||
.next("bad1")
|
||||
.then(result => {
|
||||
expect(result.value).toBe(1);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
injectedCompletionGeneratorObject
|
||||
.return(3)
|
||||
.then(result => {
|
||||
expect(result.value).toBe(2);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
injectedCompletionGeneratorObject
|
||||
.next("bad3")
|
||||
.then(result => {
|
||||
expect(result.value).toBe(3);
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
injectedCompletionGeneratorObject
|
||||
.next("bad4")
|
||||
.then(result => {
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be an AsyncGenerator object", () => {
|
||||
async function* generator() {}
|
||||
let rejection = null;
|
||||
generator.prototype.return.call("foo").catch(error => {
|
||||
rejection = error;
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
expect(rejection).toBeInstanceOf(TypeError);
|
||||
expect(rejection.message).toBe("Not an object of type AsyncGenerator");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,132 @@
|
|||
describe("correct behavior", () => {
|
||||
async function* emptyGeneratorFunction() {}
|
||||
|
||||
test("length is 1", () => {
|
||||
expect(emptyGeneratorFunction.prototype.throw).toHaveLength(1);
|
||||
});
|
||||
|
||||
const emptyGenerator = emptyGeneratorFunction();
|
||||
|
||||
test("throw from SuspendedStart", () => {
|
||||
emptyGenerator
|
||||
.throw(1337)
|
||||
.then(() => {
|
||||
expect().fail("Generator did NOT throw an unhandled exception.");
|
||||
})
|
||||
.catch(e => {
|
||||
expect(e).toBe(1337);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
test("throw from Completed", () => {
|
||||
emptyGenerator
|
||||
.throw(123)
|
||||
.then(() => {
|
||||
expect().fail("Generator did NOT throw an unhandled exception.");
|
||||
})
|
||||
.catch(e => {
|
||||
expect(e).toBe(123);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
async function* generatorTwo() {
|
||||
yield 1337;
|
||||
yield 123;
|
||||
}
|
||||
|
||||
const generatorTwoIterator = generatorTwo();
|
||||
|
||||
test("throw from SuspendedYield", () => {
|
||||
generatorTwoIterator
|
||||
.next("bad1")
|
||||
.then(result => {
|
||||
expect(result.value).toBe(1337);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
generatorTwoIterator
|
||||
.throw(999)
|
||||
.then(() => {
|
||||
expect().fail("Generator did NOT throw an unhandled exception.");
|
||||
})
|
||||
.catch(e => {
|
||||
expect(e).toBe(999);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
generatorTwoIterator
|
||||
.next("bad2")
|
||||
.then(result => {
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
async function* injectedCompletionGenerator() {
|
||||
try {
|
||||
yield 1;
|
||||
} catch (e) {
|
||||
yield e;
|
||||
}
|
||||
}
|
||||
|
||||
const injectedCompletionGeneratorObject = injectedCompletionGenerator();
|
||||
|
||||
test("throw completion is injected into generator", () => {
|
||||
injectedCompletionGeneratorObject
|
||||
.next("bad1")
|
||||
.then(result => {
|
||||
expect(result.value).toBe(1);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
injectedCompletionGeneratorObject
|
||||
.throw(9999)
|
||||
.then(result => {
|
||||
expect(result.value).toBe(9999);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
injectedCompletionGeneratorObject
|
||||
.next("bad2")
|
||||
.then(result => {
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be an AsyncGenerator object", () => {
|
||||
async function* generator() {}
|
||||
let rejection = null;
|
||||
generator.prototype.throw.call("foo").catch(error => {
|
||||
rejection = error;
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
expect(rejection).toBeInstanceOf(TypeError);
|
||||
expect(rejection.message).toBe("Not an object of type AsyncGenerator");
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue