caf
Version:
Cancelable Async Flows: a wrapper to treat generators as cancelable async functions
1,477 lines (1,263 loc) • 32.2 kB
JavaScript
// load in Node only
if (typeof EventEmitter3 == "undefined") {
global.EventEmitter3 = require("events");
}
QUnit.test( "API", function test(assert){
assert.expect( 6 );
assert.ok( _isFunction( CAF ), "CAF()" );
assert.ok( _hasProp( CAF, "cancelToken" ), "CAF.cancelToken" );
assert.ok( _isFunction( CAF.cancelToken ), "CAF.cancelToken()" );
assert.ok( _isObject( (new CAF.cancelToken()).signal ), "CAF.cancelToken#signal" );
assert.ok( _isObject( (new CAF.cancelToken()).signal.pr ), "CAF.cancelToken#signal.pr" );
assert.ok( _isFunction( (new CAF.cancelToken()).abort ), "CAF.cancelToken#abort()" );
} );
QUnit.test( "cancelToken.abort()", async function test(assert){
function checkParameter(reason) {
assert.step(reason);
}
var token = new CAF.cancelToken();
var rExpected = [
"quit",
"quit",
];
token.signal.pr.catch(checkParameter);
token.abort("quit");
token.signal.pr.catch(checkParameter);
await token.signal.pr.catch(_=>1);
// rActual;
assert.expect( 3 ); // note: 1 assertion + 2 `step(..)` calls
assert.verifySteps( rExpected, "cancelation reason passed" );
} );
QUnit.test( "CAF() + this + parameters + return", async function test(assert){
function *checkParameters(signal,a,b,...args) {
assert.step(this.x);
assert.step(String(signal === token.signal));
assert.step(String(a));
assert.step(String(b));
assert.step(String(args.length === 0));
return 42;
}
var token = new CAF.cancelToken();
var obj = { x: "obj.x", };
var rExpected = [
"obj.x",
"true",
"3",
"12",
"true",
];
var pExpected = "[object Promise]";
var qExpected = 42;
var asyncFn = CAF(checkParameters);
// rActual;
var pActual = asyncFn.call(obj,token.signal,3,12);
var qActual = await pActual;
pActual = getString(pActual);
assert.expect( 9 ); // note: 4 assertions + 5 `step(..)` calls
assert.ok( _isFunction( asyncFn ), "asyncFn()" );
assert.verifySteps( rExpected, "check arguments to generator" );
assert.strictEqual( pActual, pExpected, "returns promise" );
assert.strictEqual( qActual, qExpected, "eventually returns 42" );
} );
QUnit.test( "CAF() + raw AbortController", async function test(assert){
function *main(signal,ms) {
assert.step(String(signal === ac.signal));
assert.step(String(signal.pr && typeof signal.pr == "object"));
yield CAF.delay(signal,ms);
assert.step("oops");
}
var ac = new AbortController();
main = CAF(main);
var rExpected = [
"true",
"true",
"caught 1",
"caught 2"
];
// var rActual;
try {
let pr = main(ac,20);
ac.abort();
await pr;
}
catch (err) {
assert.step("caught 1");
}
try {
await ac.signal.pr;
}
catch (err) {
assert.step("caught 2");
}
assert.expect( 5 ); // note: 1 assertions + 4 `step(..)` calls
assert.verifySteps( rExpected, "check AC and signal" );
} );
QUnit.test( "immediate exception rejection", async function test(assert){
function *main(signal,msg) {
assert.step("main");
throw msg;
assert.step("didn't get here");
}
var token = new CAF.cancelToken();
main = CAF(main);
var rExpected = [
"main",
"Oops right away!",
];
// rActual
try {
await main(token.signal,"Oops right away!");
assert.step("didn't run this");
}
catch (err) {
assert.step(err);
}
assert.expect( 3 ); // note: 1 assertions + 2 `step(..)` calls
assert.verifySteps( rExpected, "immediate exception => rejection" );
} );
QUnit.test( "cancelation + rejection", async function test(assert){
function *main(signal,ms) {
for (let i = 0; i < 5; i++) {
assert.step(`step: ${i}`);
yield CAF.delay(signal,ms);
}
}
var token = new CAF.cancelToken();
main = CAF(main);
var rExpected = [
"step: 0",
"step: 1",
"step: 2",
];
var pExpected = "Quit!";
setTimeout(function t(){
token.abort("Quit!");
},50);
// rActual;
try {
await main(token.signal,20);
}
catch (err) {
var pActual = err;
}
assert.expect( 5 ); // note: 2 assertions + 3 `step(..)` calls
assert.verifySteps( rExpected, "canceled after 3 iterations" );
assert.strictEqual( pActual, pExpected, "cancelation => rejection" );
} );
QUnit.test( "cancelation + finally", async function test(assert){
function *main(signal,ms) {
try {
for (let i = 0; i < 5; i++) {
assert.step(`step: ${i}`);
yield CAF.delay(signal,ms);
}
}
finally {
return 42;
}
}
var token = new CAF.cancelToken();
main = CAF(main);
var rExpected = [
"step: 0",
"step: 1",
"step: 2",
];
var pExpected = 42;
setTimeout(function t(){
token.abort();
},50);
// rActual;
try {
await main(token.signal,20);
}
catch (err) {
var pActual = err;
}
assert.expect( 5 ); // note: 2 assertions + 3 `step(..)` calls
assert.verifySteps( rExpected, "canceled after 3 iterations" );
assert.strictEqual( pActual, pExpected, "finally: 42" );
} );
QUnit.test( "cascading cancelation", async function test(assert){
function *main(signal,ms) {
try {
assert.step("main: 1");
yield CAF.delay(signal,ms);
var x = yield secondary(signal,ms,2);
assert.step(`main: ${x}`);
x = yield secondary(signal,ms,3);
assert.step("shouldn't happen");
}
finally {
assert.step("main: done");
}
}
function *secondary(signal,ms,v) {
try {
assert.step(`secondary: ${v}`);
yield CAF.delay(signal,ms);
return v;
}
finally {
assert.step("secondary: done");
}
}
var token = new CAF.cancelToken();
main = CAF(main);
secondary = CAF(secondary);
var rExpected = [
"main: 1",
"secondary: 2",
"secondary: done",
"main: 2",
"secondary: 3",
"main: done",
"secondary: done",
];
var pExpected = "Quit!";
setTimeout(function t(){
token.abort("Quit!");
},50);
// rActual;
try {
await main(token.signal,20);
}
catch (err) {
var pActual = err;
}
assert.expect( 9 ); // note: 2 assertions + 7 `step(..)` calls
assert.verifySteps( rExpected, "canceled during second 'secondary()' call" );
assert.strictEqual( pActual, pExpected, "Quit!" );
} );
QUnit.test( "cancelation rejection ordering", async function test(assert){
function *main(signal,ms) {
signal.pr.catch(function c(){ assert.step("main:signal.pr.catch"); });
assert.step("main: 1");
var pr = secondary(signal,ms);
pr.catch(function c(){ assert.step("main:pr.catch"); });
yield pr;
assert.step("main: shouldn't happen");
}
function *secondary(signal,ms) {
signal.pr.catch(function c() { assert.step("secondary:signal.pr.catch"); });
assert.step("secondary: 1");
yield CAF.delay(signal,ms);
assert.step("secondary: shouldn't happen");
}
var token = new CAF.cancelToken();
main = CAF(main);
secondary = CAF(secondary);
var rExpected = [
"main: 1",
"secondary: 1",
"main:signal.pr.catch",
"secondary:signal.pr.catch",
"outer:pr.catch",
"main:pr.catch",
];
var pExpected = "Quit!";
setTimeout(function t(){
token.abort("Quit!");
},30);
// rActual;
try {
var pr = main(token.signal,50);
var x = pr.catch(function c() { assert.step("outer:pr.catch"); });
await x;
await pr;
}
catch (err) {
var pActual = err;
}
assert.expect( 8 ); // note: 2 assertions + 6 `step(..)` calls
assert.verifySteps( rExpected, "rejects in expected order" );
assert.strictEqual( pActual, pExpected, "Quit!" );
} );
QUnit.test( "already aborted", async function test(assert){
function *main(signal,ms) {
assert.step("main: 1");
try {
let delayToken = new CAF.cancelToken();
delayToken.abort("aborting delayToken");
yield CAF.delay(delayToken.signal,ms);
assert.step("main: shouldn't get here");
}
catch (err) {
assert.step(getString(err));
}
assert.step("main: 2");
return "end of main";
}
function *secondary(signal) {
assert.step("secondary: shouldn't get here");
return "shouldn't return this value";
}
function *third(signal,ms) {
assert.step("third: 1");
yield CAF.delay(signal,ms);
assert.step("third: 2");
return "end of third";
}
var token1 = new CAF.cancelToken();
var token2 = new CAF.cancelToken();
var token3 = new CAF.cancelToken();
token2.abort("aborting token2");
main = CAF(main);
secondary = CAF(secondary);
third = CAF(third);
var rExpected = [
"main: 1",
"third: 1",
"aborting delayToken",
"main: 2",
"third: 2",
];
var pExpected = "end of main";
var qExpected = "aborting token2";
var tExpected = "end of third";
// rActual;
var pActual = main(token1,token1,50);
var qActual = secondary(token2,token2,25);
var tActual = third(token3,token3,10);
await CAF.delay(20);
token3.abort("aborting token3");
pActual = await pActual;
try {
await qActual;
}
catch (err) {
qActual = getString(err);
}
tActual = await tActual;
assert.expect( 9 ); // note: 4 assertions + 5 `step(..)` calls
assert.verifySteps( rExpected, "pre-aborts flow control" );
assert.strictEqual( pActual, pExpected, "main: result" );
assert.strictEqual( qActual, qExpected, "secondary: result" );
assert.strictEqual( tActual, tExpected, "third: result" );
} );
QUnit.test( "delay()", async function test(assert){
function *main(signal,ms) {
assert.step("main: 1");
yield CAF.delay(null,ms);
assert.step("main: 2");
var result = yield CAF.delay(signal,ms);
assert.step("main: 3");
return result;
}
function *secondary(signal,ms) {
assert.step("secondary: 1");
yield CAF.delay(null,ms);
assert.step("secondary: 2");
var result = yield CAF.delay(signal,ms);
assert.step("secondary: 3");
return result;
}
function *third(signal,ms) {
assert.step("third: 1");
try {
let timeoutToken = new CAF.cancelToken();
setTimeout(function t(){ timeoutToken.abort(); },ms);
yield CAF.delay(timeoutToken,ms * 10);
assert.step("third: shouldn't get here");
}
catch (err) {
assert.step(getString(err));
}
assert.step("third: 3");
return "end of third";
}
var token = new CAF.cancelToken();
main = CAF(main);
secondary = CAF(secondary);
third = CAF(third);
var rExpected = [
"main: 1",
"secondary: 1",
"third: 1",
"secondary: 2",
"main: 2",
"delay (600) interrupted",
"third: 3",
"secondary: 3",
"main: 3",
];
var pExpected = [
"delayed: 50",
"delayed: 40",
"end of third",
];
// rActual;
var pActual = await Promise.all([
main(token,50),
secondary(token,40),
third(token,60),
]);
assert.expect( 11 ); // note: 1 assertions + 9 `step(..)` calls
assert.verifySteps( rExpected, "delays in expected order" );
assert.deepEqual( pActual, pExpected, "returns from successful delay()" );
} );
QUnit.test( "timeout()", async function test(assert){
function *main(signal,ms) {
assert.step("main: 1");
yield CAF.delay(signal,ms);
assert.step("main: shouldn't get here");
return "shouldn't return this value";
}
function *secondary(signal,ms) {
assert.step("secondary: 1");
yield CAF.delay(signal,ms);
assert.step("secondary: 2");
return "end of secondary";
}
function *third(signal,ms) {
assert.step("third: shouldn't get here");
return "shouldn't return this value";
}
var timeoutToken1 = new CAF.timeout(20);
var timeoutToken2 = new CAF.timeout(75,"timeout 2");
var timeoutToken3 = new CAF.timeout();
timeoutToken3.abort("timeout 3");
timeoutToken3.abort("timeout 3!!!!!");
main = CAF(main);
secondary = CAF(secondary);
third = CAF(third);
var rExpected = [
"main: 1",
"secondary: 1",
"secondary: 2",
];
var pExpected = "Timeout";
var qExpected = "end of secondary";
var tExpected = "timeout 3";
// rActual;
var pActual = main(timeoutToken1,50);
var qActual = secondary(timeoutToken2,40);
var tActual = third(timeoutToken3,60);
try {
await pActual;
}
catch (err) {
pActual = getString(err);
}
qActual = await qActual;
try {
await tActual;
}
catch (err) {
tActual = getString(err);
}
assert.expect( 7 ); // note: 4 assertions + 3 `step(..)` calls
assert.verifySteps( rExpected, "timeouts flow control" );
assert.strictEqual( pActual, pExpected, "main: result" );
assert.strictEqual( qActual, qExpected, "secondary: result" );
assert.strictEqual( tActual, tExpected, "third: result" );
} );
QUnit.test( "signalRace()", async function test(assert){
function *main(signal,ms) {
assert.step("main: 1");
yield CAF.delay(signal,ms);
assert.step("main: 2");
yield CAF.delay(signal,ms);
assert.step("main: shouldn't get here");
return "shouldn't return this value";
}
var ac = new AbortController();
CAF.delay(60).then(function t(){ ac.abort(); });
var timeoutToken1 = new CAF.timeout(90);
var timeoutToken2 = new CAF.timeout(45,"Timeout2");
main = CAF(main);
var rExpected = [
"main: 1",
"main: 2",
];
var pExpected = "Timeout2";
// rActual;
var pActual = main(
CAF.signalRace([
timeoutToken1.signal,
ac.signal,
timeoutToken2.signal,
]),
30
);
try {
await pActual;
}
catch (err) {
pActual = getString(err);
}
assert.expect( 4 ); // note: 2 assertions + 2 `step(..)` calls
assert.verifySteps( rExpected, "signal race() flow control" );
assert.strictEqual( pActual, pExpected, "main: result" );
} );
QUnit.test( "signalAll()", async function test(assert){
function *main(signal,ms) {
assert.step("main: 1");
yield CAF.delay(signal,ms);
assert.step("main: 2");
yield CAF.delay(signal,ms);
assert.step("main: shouldn't get here");
return "shouldn't return this value";
}
var ac = new AbortController();
CAF.delay(50).then(function t(){ ac.abort(); });
var timeoutToken1 = new CAF.timeout(60);
var timeoutToken2 = new CAF.timeout(25,"Timeout2");
main = CAF(main);
var rExpected = [
"main: 1",
"main: 2",
];
var pExpected = [
"Timeout",
"undefined",
"Timeout2",
];
// rActual;
var pActual = main(
CAF.signalAll([
timeoutToken1.signal,
ac.signal,
timeoutToken2.signal,
]),
40
);
try {
await pActual;
}
catch (err) {
pActual = err;
pActual[1] = typeof pActual[1];
}
assert.expect( 4 ); // note: 2 assertions + 2 `step(..)` calls
assert.verifySteps( rExpected, "signal all() flow control" );
assert.deepEqual( pActual, pExpected, "main: result" );
} );
QUnit.test( "checking aborted reason", async function test(assert){
var cancelReason = 'testing cancel req';
function *main(signal, ms) {
try {
for (let i = 0; i < 5; i++) {
assert.step(`step: ${i}`);
yield CAF.delay(signal,ms);
}
}
finally {
if (signal.aborted) {
assert.step("step: in canceled finally");
assert.strictEqual(signal.reason, cancelReason, 'unexpected cancel reason');
}
else {
assert.step("step: uhoh signal should be aborted");
}
}
}
var token = new CAF.cancelToken();
main = CAF(main);
var rExpected = [
"step: 0",
"step: 1",
"step: 2",
"step: in canceled finally"
];
var pExpected = cancelReason;
setTimeout(function t(){
token.abort(cancelReason);
// call abort a second time right away to make sure it doesn't change things
token.abort("uhoh reason");
},50);
// rActual;
try {
await main(token.signal,20);
}
catch (err) {
var pActual = err;
}
assert.expect( 7 ); // note: 3 assertions + 4 `step(..)` calls
assert.verifySteps( rExpected, "ignore canceled after 3 iterations" );
assert.strictEqual( pActual, pExpected, "unexpected final result" );
} );
QUnit.test( "checking aborted reason exists + raw AbortController", async function test(assert){
function *main(signal, ms) {
try {
for (let i = 0; i < 5; i++) {
assert.step(`step: ${i}`);
yield CAF.delay(signal,ms);
}
}
finally {
if (signal.aborted) {
assert.step("step: in canceled finally");
}
}
}
var ac = new AbortController();
main = CAF(main);
var rExpected = [
"step: 0",
"step: 1",
"step: 2",
"step: in canceled finally"
];
setTimeout(function t(){
ac.abort();
// try to forcibly fire the event even after
// the token was already aborted
ac.signal.dispatchEvent(new Event("abort"));
},50);
// rActual;
try {
await main(ac,20);
}
catch (err) {
var pActual = (err === undefined) ? "ac.abort()" : err;
}
assert.expect( 6 ); // note: 2 assertions + 4 `step(..)` calls
assert.verifySteps( rExpected, "ignore canceled after 3 iterations" );
assert.ok( pActual && pActual === "ac.abort()", "expected final abort event to be thrown" );
} );
QUnit.test( "discard()", async function test(assert){
function *main(signal,ms) {
assert.step("step 1");
yield CAF.delay(signal,ms);
assert.step("step 2");
yield CAF.delay(signal,ms);
assert.step("step 3");
yield CAF.delay(signal,ms);
assert.step("step 4");
}
var token = new CAF.cancelToken();
main = CAF(main);
var rExpected = [
"step 1",
"step 2",
"step 3",
"step 4",
];
setTimeout(function t(){
token.discard();
token.discard();
},40);
setTimeout(function t(){
token.abort();
},50);
// rActual;
try {
await main(token,30);
}
catch (err) {
var pActual = err;
}
assert.expect( 6 ); // note: 2 assertions + 4 `step(..)` calls
assert.verifySteps( rExpected, "cancelation ignored because the token was discarded" );
assert.ok( pActual === undefined, "normal completion with no abort being thrown" );
} );
QUnit.test( "token cycle", async function test(assert){
function *main(signal,counter,ms) {
assert.step(`step 1: ${counter}`);
yield CAF.delay(signal,ms);
assert.step(`step 2: ${counter}`);
yield CAF.delay(signal,ms);
assert.step("should not reach here");
}
var getNextToken = CAF.tokenCycle();
main = CAF(main);
var rExpected = [
"step 1: 0",
"catch(0): re-requesting(1)",
"step 1: 1",
"catch(1): re-requesting(2)",
"step 1: 2",
"catch(2): re-requesting(3)",
"catch(3): already canceled",
"step 1: 4",
"step 2: 4",
"catch(4): please stop",
];
var token;
var waitPr;
for (let i = 0; i < 5; i++) {
token = getNextToken(/*reason=*/`re-requesting(${i})`);
// prematurely cancel this token before even using it
if (i == 3) {
token.abort("already canceled");
}
// give time for the cancellation to be fully
// processed
await CAF.delay(10);
waitPr = main(token,i,50)
.then(function t() {
assert.step("should not reach here either");
})
.catch(function c(err){
assert.step(`catch(${i}): ${err}`);
});
if (i < 4) {
await CAF.delay(30);
}
else {
await CAF.delay(75);
token.abort("please stop");
}
}
// make sure to wait for all of the test steps to
// complete...
await waitPr;
assert.expect( 11 ); // note: 1 assertions + 10 `step(..)` calls
assert.verifySteps( rExpected, "expected token cycle" );
} );
QUnit.test( "async-generator: loop iteration", async function test(assert){
function *main({ signal, pwait },ms) {
assert.step("step 1");
yield pwait(CAF.delay(signal,ms));
assert.step("step 2");
yield "step 3";
assert.step("step 4");
yield pwait(CAF.delay(signal,ms));
assert.step("step 5");
yield Promise.resolve("step 6");
}
var token = new CAF.cancelToken();
main = CAG(main);
var rExpected = [
"step 1",
"step 2",
"step 3",
"step 4",
"step 5",
"step 6",
];
// var rActual;
for await (let msg of main(token,10)) {
assert.step(msg);
}
assert.expect( 7 ); // note: 1 assertions + 6 `step(..)` calls
assert.verifySteps( rExpected, "iteration steps" );
} );
QUnit.test( "async-generator: manual iteration", async function test(assert){
function *main({ signal, pwait },ms) {
assert.step("step 1");
yield pwait(CAF.delay(signal,ms));
assert.step("step 2");
var nextStep = yield "step 3";
assert.step(nextStep);
yield pwait(CAF.delay(signal,ms));
assert.step("step 5");
yield Promise.resolve("step 6");
return "step 7";
}
var token = new CAF.cancelToken();
main = CAG(main);
var it = main(token,10);
var res;
var rExpected = [
"step 1",
"step 2",
"step 3",
"step 4",
"step 5",
"step 6",
"step 7",
];
// var rActual;
while (true) {
let ret = await it.next(res);
assert.step(ret.value);
if (ret.value == "step 3") {
res = "step 4";
}
else {
res = undefined;
}
if (ret.done) {
break;
}
}
assert.expect( 8 ); // note: 1 assertions + 7 `step(..)` calls
assert.verifySteps( rExpected, "iteration steps" );
} );
QUnit.test( "async-generator: iteration exception recovery", async function test(assert){
async function exception(signal,msg,ms) {
await CAF.delay(signal,ms);
throw msg;
}
function *main({ signal, pwait },ms) {
assert.step("step 1");
yield pwait(CAF.delay(signal,ms));
try {
yield pwait(exception(signal,"step 2",ms));
}
catch (err) {
assert.step(err);
}
yield "step 3";
assert.step("step 4");
yield pwait(CAF.delay(signal,ms));
assert.step("step 5");
yield Promise.resolve("step 6");
}
var token = new CAF.cancelToken();
main = CAG(main);
var rExpected = [
"step 1",
"step 2",
"step 3",
"step 4",
"step 5",
"step 6",
];
// var rActual;
for await (let msg of main(token,10)) {
assert.step(msg);
}
assert.expect( 7 ); // note: 1 assertions + 6 `step(..)` calls
assert.verifySteps( rExpected, "iteration steps" );
} );
QUnit.test( "async-generator: timeout aborted iteration", async function test(assert){
function *main({ signal, pwait },ms) {
assert.step("step 1");
yield pwait(CAF.delay(signal,ms));
assert.step("step 2");
yield "step 3";
assert.step("step 4");
yield pwait(CAF.delay(signal,ms));
assert.step("should not get here");
}
var token = new CAF.timeout(75);
main = CAG(main);
var rExpected = [
"step 1",
"step 2",
"step 3",
"step 4",
"Timeout",
];
try {
// var rActual;
for await (let msg of main(token,50)) {
assert.step(msg);
}
}
catch (err) {
assert.step(err);
}
assert.expect( 6 ); // note: 1 assertions + 5 `step(..)` calls
assert.verifySteps( rExpected, "iteration steps" );
} );
QUnit.test( "async-generator: token aborted iteration", async function test(assert){
function *main({ signal, pwait },ms) {
assert.step("step 1");
yield pwait(CAF.delay(signal,ms));
assert.step("step 2");
yield Promise.resolve("step 3");
assert.step("step 4");
yield pwait(CAF.delay(signal,ms));
assert.step("should not get here");
}
var token = new CAF.cancelToken();
main = CAG(main);
var rExpected = [
"step 1",
"step 2",
"step 3",
"step 4",
"Canceled!",
"Canceled!",
];
setTimeout(function waitToCancel(){
token.abort("Canceled!");
},75);
try {
// var rActual;
for await (let msg of main(token,50)) {
assert.step(msg);
}
}
catch (err) {
assert.step(err);
}
try {
main(token,50).next();
}
catch (err) {
assert.step(err);
}
assert.expect( 7 ); // note: 1 assertions + 6 `step(..)` calls
assert.verifySteps( rExpected, "iteration steps" );
} );
QUnit.test( "async-generator: abort-controller aborted iteration", async function test(assert){
function *main({ signal, pwait },ms) {
try {
assert.step("step 1");
yield pwait(CAF.delay(signal,ms));
assert.step("step 2");
yield "step 3";
assert.step("step 4");
yield pwait(CAF.delay(signal,ms));
assert.step("should not get here");
}
finally {
return "step 5";
}
}
var ac = new AbortController();
main = CAG(main);
var rExpected = [
"step 1",
"step 2",
"step 3",
"step 4",
"step 5",
"Aborted",
];
setTimeout(function waitToCancel(){
ac.abort();
},75);
try {
// var rActual;
for await (let msg of main(ac,50)) {
assert.step(msg);
}
}
catch (err) {
assert.step(err);
}
try {
main(ac,50).next();
}
catch (err) {
assert.step(err);
}
assert.expect( 7 ); // note: 1 assertions + 6 `step(..)` calls
assert.verifySteps( rExpected, "iteration steps" );
} );
QUnit.test( "async-generator: iterator return", async function test(assert){
function *main({ signal, pwait },ms) {
try {
assert.step("step 1");
yield pwait(CAF.delay(signal,ms));
assert.step("step 2");
yield "step 3";
assert.step("step 4");
yield pwait(CAF.delay(signal,ms));
assert.step("should not get here");
}
finally {
return "step 5";
}
}
var token = new CAF.cancelToken();
main = CAG(main);
var it = main(token,50);
var rExpected = [
"step 1",
"step 2",
"step 3",
"step 4",
"step 5",
"already complete",
];
setTimeout(async function waitToCancel(){
var ret = await it.return("returned");
assert.step(ret.value);
},75);
try {
// var rActual;
for await (let msg of it) {
assert.step(msg);
}
}
catch (err) {
assert.step(String(err));
}
var ret = await it.return("already complete");
assert.step(ret.value);
assert.expect( 7 ); // note: 1 assertions + 6 `step(..)` calls
assert.verifySteps( rExpected, "iteration steps" );
} );
QUnit.test( "async-generator: onEvent", async function test(assert){
var token = new CAF.cancelToken();
var token3 = new CAF.cancelToken();
var token4 = new CAF.cancelToken();
var events = new EventEmitter3();
var eventStream1 = CAG.onEvent(token,events,"msg1");
var eventStream2 = CAG.onEvent(token,events,"msg2");
var eventStream3 = CAG.onEvent(token3,events,"msg3");
var eventStream4 = CAG.onEvent(token4,events,"msg4");
var eventStream5 = CAG.onEvent(CAF.timeout(250),events,"msg5");
var rExpected = [
"counter(1): 2",
"counter(1): 3",
"counter(1): 4",
"counter(2): 0",
"counter(2): 1",
"counter(2): 2",
"counter(2): 3",
"counter(2): 4",
"counter(2): 5",
"counter(2): 6",
"counter(2): 7",
"eventStream(3) stopped: aborting(3)",
"eventStream(4) stopped: aborting(4)",
"counter(5): 8",
"counter(5): 9",
"counter(5): 10",
"counter(5): 11",
"eventStream(5) stopped: Timeout"
];
var counter = 0;
var intv = setInterval(function emits(){
events.emit("msg1",`counter(1): ${counter}`);
events.emit("msg2",`counter(2): ${counter}`);
events.emit("msg3",`counter(3): ${counter}`);
token3.abort("aborting(3)");
events.emit("msg4",`counter(4): ${counter}`);
if (counter > 5) {
token4.abort("aborting(4)");
}
events.emit("msg5",`counter(5): ${counter}`);
counter++;
},20);
// emit some events that should be ignored because
// the event streams are not yet listening
events.emit("msg1","ignored event(1)");
events.emit("msg2","ignored event(2)");
events.emit("msg3","ignored event(3)");
events.emit("msg4","ignored event(4)");
// force one stream to start listening right away
eventStream2.start();
// wait to start iterating the streams, to let
// some events build up in the buffer
await CAF.delay(50);
try {
for await (let e1 of eventStream1) {
// make sure the start() method doesn't mess
// up an already started stream
eventStream1.start();
assert.step(e1);
if (counter > 4) break;
}
}
catch (err) {
assert.step(`no throw(1): ${err}`);
}
try {
for await (let e2 of eventStream2) {
assert.step(e2);
if (counter > 7) eventStream2.return();
}
}
catch (err) {
assert.step(`no throw(2): ${err}`);
}
try {
for await (let e3 of eventStream3) {
assert.step("should not get here(3)");
break;
}
}
catch (err) {
assert.step(`eventStream(3) stopped: ${err}`);
}
try {
for await (let e4 of eventStream4) {
assert.step("should not get here(4)");
break;
}
}
catch (err) {
assert.step(`eventStream(4) stopped: ${err}`);
}
try {
for await (let e5 of eventStream5) {
assert.step(e5);
// this is just a fail-safe to prevent
// a run-away test
if (counter > 50) break;
}
}
catch (err) {
assert.step(`eventStream(5) stopped: ${err}`);
}
await CAF.delay(20);
clearInterval(intv);
assert.expect( 19 ); // note: 1 assertions + 18 `step(..)` calls
assert.verifySteps( rExpected, "events received" );
} );
QUnit.test( "async-generator: onEvent, manual iteration", async function test(assert){
var token = new CAF.cancelToken();
var events = new EventEmitter3();
var eventStream = CAG.onEvent(token,events,"msg");
var rExpected = [
"first messages sent",
"counter: 0",
"counter: 1",
"buffered messages received",
"last messages sent",
"counter: 2",
"counter: 3",
"counter: 4",
"counter: 5",
];
var prs = [];
// pre-request events from the stream
for (let i = 0; i < 4; i++) {
prs.push(eventStream.next());
}
await CAF.delay(20);
// push some messages into the stream
for (let i = 0; i < 2; i++) {
events.emit("msg",`counter: ${i}`);
}
assert.step("first messages sent");
setTimeout(function moreMessages(){
for (let i = 2; i < 6; i++) {
events.emit("msg",`counter: ${i}`);
}
assert.step("last messages sent");
setTimeout(function after(){
eventStream.return();
},0);
},20);
setTimeout(function after(){
assert.step("buffered messages received");
},0);
// consume messages from the stream
for (let pr of prs) {
let res = await pr;
if (!res.done) {
assert.step(res.value);
}
else break;
}
for await (let v of eventStream) {
assert.step(v);
}
assert.expect( 10 ); // note: 1 assertions + 9 step(..)` calls
assert.verifySteps( rExpected, "events received" );
} );
QUnit.test( "async-generator: onceEvent", async function test(assert){
var token = new CAF.cancelToken();
var token3 = new CAF.cancelToken();
var token4 = new CAF.cancelToken();
var events = new EventEmitter3();
var pr1 = CAG.onceEvent(token,events,"msg1");
var pr2 = CAG.onceEvent(token,events,"msg2");
var pr3 = CAG.onceEvent(token3,events,"msg3");
var pr4 = CAG.onceEvent(token4,events,"msg4");
var pr5 = CAG.onceEvent(CAF.timeout(10),events,"msg5");
var rExpected = [
"counter(1): 0",
"counter(2): 2",
"counter(3): 0",
"throws(4): aborting(4)",
"throws(5): Timeout",
];
var counter = 0;
var intv = setInterval(function emits(){
events.emit("msg1",`counter(1): ${counter}`);
if (counter > 1) {
events.emit("msg2",`counter(2): ${counter}`);
}
events.emit("msg3",`counter(3): ${counter}`);
if (counter > 0) {
token3.abort("aborting(3)");
}
events.emit("msg4",`counter(4): ${counter}`);
token4.abort("aborting(4)");
events.emit("msg5",`counter(5): ${counter}`);
counter++;
},20);
// wait to read the event promises, to let
// multiple events have a chance to fire
await CAF.delay(50);
var listeners1 = events.listeners("msg1");
var listeners3 = events.listeners("msg3");
var listeners4 = events.listeners("msg4");
var listeners5 = events.listeners("msg5");
try {
// wait for an event from pr1
let msg1 = await pr1;
assert.step(msg1);
// make sure event already unsubscribed
if (listeners1.length > 0) {
throw "event still subscribed";
}
}
catch (err) {
assert.step(`no throw(1): ${err}`);
}
try {
// wait for an event from pr2
let msg2 = await pr2;
assert.step(msg2);
// make sure event already unsubscribed
if (events.listeners("msg2").length > 0) {
throw "event still subscribed";
}
}
catch (err) {
assert.step(`no throw(2): ${err}`);
}
try {
// wait for an event from pr3
let msg3 = await pr3;
assert.step(msg3);
// make sure event already unsubscribed
if (listeners3.length > 0) {
throw "event still subscribed";
}
}
catch (err) {
assert.step(`no throw(3): ${err}`);
}
try {
// wait for an event from pr4
let msg4 = await pr4;
assert.step(`should not get here: ${msg4}`);
// make sure event already unsubscribed
if (listeners4.length > 0) {
assert.step("event still subscribed");
}
}
catch (err) {
assert.step(`throws(4): ${err}`);
}
try {
// wait for an event from pr5
let msg5 = await pr5;
assert.step(`should not get here: ${msg5}`);
// make sure event already unsubscribed
if (listeners5.length > 0) {
assert.step("event still subscribed");
}
}
catch (err) {
assert.step(`throws(5): ${err}`);
}
await CAF.delay(50);
clearInterval(intv);
assert.expect( 6 ); // note: 1 assertions + 5 `step(..)` calls
assert.verifySteps( rExpected, "events received" );
} );
function _hasProp(obj,prop) {
return Object.hasOwnProperty.call( obj, prop );
}
function _isFunction(v) {
return typeof v == "function";
}
function _isObject(v) {
return v && typeof v == "object" && !_isArray( v );
}
function _isArray(v) {
return Array.isArray( v );
}
function getString(v) {
try {
return (v && _isFunction(v.toString)) ? v.toString() : String(v);
}
catch (err) {
return "";
}
}
;