Comparing version 2.0.0 to 3.0.0
/*! caf.js | ||
v2.0.0 (c) 2018 Kyle Simpson | ||
v3.0.0 (c) 2018 Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
@@ -81,19 +81,17 @@ */ | ||
class cancelSignal extends AbortSignal { | ||
class cancelToken { | ||
constructor() { | ||
super(); | ||
this.controller = new AbortController(); | ||
this.pr = new Promise((_,rej)=>this.rej = rej); | ||
this.pr.catch(_=>1); // silence unhandled rejection warnings | ||
// silence unhandled rejection warnings | ||
this.pr.catch(_=>1); | ||
} | ||
} | ||
class cancelToken extends AbortController { | ||
constructor() { | ||
super(); | ||
this.signal = new cancelSignal(); | ||
} | ||
abort(reason) { | ||
super.abort(); | ||
this.signal.rej(reason); | ||
this.rej(reason); | ||
this.controller.abort(); | ||
} | ||
get signal() { | ||
this.controller.signal.pr = this.pr; | ||
return this.controller.signal; | ||
} | ||
} | ||
@@ -110,5 +108,5 @@ | ||
function CAF(generatorFn) { | ||
return function instance(cancelToken,...args){ | ||
var { it, pr } = _runner.call(this,generatorFn,cancelToken,...args); | ||
var cancel = cancelToken.pr.catch(function onCancel(reason){ | ||
return function instance(signal,...args){ | ||
var { it, success } = _runner.call(this,generatorFn,signal,...args); | ||
var cancelation = signal.pr.catch(function onCancel(reason){ | ||
try { | ||
@@ -118,7 +116,7 @@ var ret = it.return(); | ||
} | ||
finally { it = pr = cancel = null; } | ||
finally { it = success = cancelation = null; } | ||
}); | ||
var race = Promise.race([ pr, cancel ]); | ||
race.catch(_=>1); // silence unhandled rejection warnings | ||
return race; | ||
var completion = Promise.race([ success, cancelation ]); | ||
completion.catch(_=>1); // silence unhandled rejection warnings | ||
return completion; | ||
}; | ||
@@ -136,3 +134,3 @@ } | ||
it, | ||
pr: Promise.resolve( | ||
success: Promise.resolve( | ||
(function handleNext(value){ | ||
@@ -139,0 +137,0 @@ // run to the next yielded value |
{ | ||
"name": "async-caf", | ||
"version": "2.0.0", | ||
"version": "3.0.0", | ||
"description": "Wrapper for generators as cancelable async functions", | ||
@@ -5,0 +5,0 @@ "main": "./dist/caf.js", |
@@ -13,3 +13,3 @@ # Cancelable Async Functions (CAF) | ||
This library uses ES6 (aka ES2015) features. If you need to support environments prior to ES6, transpile it first (with Babel, etc). | ||
This utility uses ES6 (aka ES2015) features. If you need to support environments prior to ES6, transpile it first (with Babel, etc). | ||
@@ -46,3 +46,3 @@ ## At A Glance | ||
Moreover, the generator itself is provided the cancelation token's signal (`signal` parameter above), so you can call another `function*` generator with **CAF**, and pass along the shared cancelation token signal. In this way, a single cancelation signal cascades across however many `function*` generators are currently in the execution chain: | ||
Moreover, the generator itself is provided the cancelation token's `signal`, so you can call another `function*` generator via **CAF** and pass along that shared `signal`. In this way, a single cancelation signal can cascade across all the **CAF**-wrapped functions in the chain of execution: | ||
@@ -53,7 +53,7 @@ ```js | ||
var one = CAF( function *one(signal,v){ | ||
return yield two(signal,v); | ||
return yield two( signal, v ); | ||
} ); | ||
var two = CAF( function *two(signal,v){ | ||
return yield three(signal,v); | ||
return yield three( signal, v ); | ||
} ); | ||
@@ -79,13 +79,13 @@ | ||
An `async function` and a `function*` generator (driven with a [generator-runner](https://github.com/getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch4.md#promise-aware-generator-runner)) look, generally speaking, very similar. For that reason, most people just prefer `async function` since it's a little nicer syntax and doesn't require a library to provide the runner. | ||
Generally speaking, an `async function` and a `function*` generator (driven with a [generator-runner](https://github.com/getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch4.md#promise-aware-generator-runner)) look very similar. For that reason, most people just prefer the `async function` form since it's a little nicer syntax and doesn't require a library for the runner. | ||
However, there are several limitations to `async function`s inherent to having the syntax and engine make implicit assumptions that you otherwise have to explicitly handle with `function*` generators. | ||
However, there are limitations to `async function`s inherent to having the syntax and engine make implicit assumptions that you otherwise have to explicitly handle with `function*` generators. | ||
One clear example of this limitation is that an `async function` cannot be externally canceled once it starts running. If you want to be able to cancel it, you have to intrusively modify its definition to have it consult an external value source -- like a boolean -- at each line that you care about being a cancelation point. This is ugly and error-prone. | ||
One clear example of these limitations is that an `async function` cannot be externally canceled once it starts running. If you want to be able to cancel it, you have to intrusively modify its definition to have it consult an external value source -- like a boolean or promise -- at each line that you care about being a cancelation point. This is ugly and error-prone. | ||
`function*` generators by contrast can be aborted at any time, using the iterator's `return(..)` method. But the downside of `function*` generators is either needing a library or the repetitive boilerplate of handling the iterator manually. | ||
`function*` generators by contrast can be aborted at any time, using the iterator's `return(..)` method. But the downside of using `function*` generators is either needing a runner utility or the repetitive boilerplate of handling the iterator manually. | ||
The best compromise is being able to call a `function*` generator like an `async function`, and providing it a cancelation token you can then use to signal that you want it to cancel. That's what **CAF** provides. | ||
The best solution would be a `function*` generator that can be called like a normal `async function`, but with a cancelation token to signal it to cancel. That's what **CAF** provides. | ||
The `CAF(..)` function takes a `function*` generator, and returns a regular function that expects arguments, much the same as if it was a normal `async function`. The only observable difference is that this function should be provided the cancelation token as its first argument, with any other arguments passed subsequent, as desired. | ||
The `CAF(..)` function takes a `function*` generator, and returns a regular function that expects any arguments, much the same as if it was a normal `async function`. Other than minor syntactic aesthetics, the most observable difference is that a **CAF**-wrapped function should be provided a cancelation token's `signal` as its first argument, with any other arguments passed subsequent, as desired. | ||
@@ -108,3 +108,3 @@ ## Overview | ||
Both `one(..)` and `two(..)` can be called directly, with argument(s), and both return a promise for their completion: | ||
Both `one(..)` and `two(..)` can be called directly with argument(s), and both return a promise for their completion: | ||
@@ -121,3 +121,3 @@ ```js | ||
If `token.abort(..)` is executed while `two(..)` is still running, the signal's promise will be rejected. If you pass a cancelation reason (any value, but typically a string) to `token.abort(..)`, that will be the promise rejection reason: | ||
If `token.abort(..)` is executed while `two(..)` is still running, the `signal`'s promise will be rejected. If you pass a cancelation reason (any value, but typically a string) to `token.abort(..)`, that will be the promise rejection reason: | ||
@@ -133,5 +133,5 @@ ```js | ||
Canceling a **CAF**-wrapped `function*` generator that is paused does cause it to abort right away, but if there's a pending `finally {..}` clause, that will still have a chance to run. | ||
Canceling a **CAF**-wrapped `function*` generator that is paused causes it to abort right away, but if there's a pending `finally {..}` clause, that will still have a chance to run. | ||
Moreover, a `return` of a non-`undefined` value in that pending `finally {..}` clause will override the completion result of the function: | ||
Moreover, a `return` of any non-`undefined` value in that pending `finally {..}` clause will override the promise rejection reason: | ||
@@ -156,7 +156,7 @@ ```js | ||
Whatever value is passed to `abort(..)`, if any, is normally the completion value (promise rejection reason) of the function. But in this case, `42` overrides the `"Aborting!"` value. | ||
Whatever value is passed to `abort(..)`, if any, is normally set as the promise rejection reason. But in this case, `return 42` overrides the `"Aborting!"` rejection reason. | ||
### `AbortController(..)` | ||
`CAF.cancelToken(..)` extends [`AbortController`, the DOM standard](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for canceling/aborting operations like `fetch(..)` calls. As such, a cancelation token's signal can be passed directly to a DOM API like `fetch(..)` and it will respond to it accordingly: | ||
`CAF.cancelToken(..)` instantiates [`AbortController`, the DOM standard](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for canceling/aborting operations like `fetch(..)` calls. As such, a cancelation token's `signal` can be passed directly to a DOM method like `fetch(..)`, which will respond to it accordingly: | ||
@@ -179,7 +179,7 @@ ```js | ||
**Note:** If the standard `AbortController` is not defined in the environment, it's [polyfilled](https://github.com/mo/abortcontroller-polyfill). | ||
**Note:** If the standard `AbortController` is not defined in the environment, it's [polyfilled](https://github.com/mo/abortcontroller-polyfill) by **CAF**. In such a case, `fetch(..)` and other such DOM methods will likely not actually respond to the cancelation signal. | ||
### Manual Cancelation Signal Handling | ||
Even if you aren't calling a cancelation signal-aware utility (like `fetch(..)`), you can still manually listen to the cancelation signal: | ||
Even if you aren't calling a cancelation signal-aware utility (like `fetch(..)`), you can still manually respond to the cancelation `signal` via its attached promise: | ||
@@ -207,5 +207,5 @@ ```js | ||
**Note:** The `catch(..)` handler inside of `main(..)` will still run, even though `main(..)` will be aborted at its waiting `yield` statement. If there was a way to manually cancel the `ajax(..)` call, that code could run here. | ||
**Note:** The `catch(..)` handler inside of `main(..)` will still run, even though `main(..)` itself will be aborted at its waiting `yield` statement. If there was a way to manually cancel the `ajax(..)` call, that code could run in the `catch(..)` handler. | ||
And even if you aren't running in a **CAF**-wrapped function, you can still respond to the cancelation signal manually to interrupt flow control: | ||
And even if you aren't running a **CAF**-wrapped function, you can still respond to the cancelation `signal`'s promise manually to affect flow control: | ||
@@ -223,2 +223,3 @@ ```js | ||
// this won't run if `signal.pr` rejects | ||
console.log( resp ); | ||
@@ -238,3 +239,3 @@ return resp; | ||
**Note:** As discussed earlier, the `ajax(..)` call itself is not cancelation aware, and is thus not being aborted here. But we *are* aborting our waiting on the `ajax(..)` call. When `signal.pr` wins the `Promise.race(..)` race and creates an exception from its promise rejection, flow control jumps to the `catch (err) { .. }` clause. | ||
**Note:** As discussed earlier, the `ajax(..)` call itself is not cancelation aware, and is thus not being aborted here. But we *are* aborting our waiting on the `ajax(..)` call. When `signal.pr` wins the `Promise.race(..)` race and creates an exception from its promise rejection, flow control jumps straight to the `catch (err) { .. }` clause. | ||
@@ -241,0 +242,0 @@ ## npm Package |
@@ -8,19 +8,17 @@ (function UMD(name,context,definition){ | ||
class cancelSignal extends AbortSignal { | ||
class cancelToken { | ||
constructor() { | ||
super(); | ||
this.controller = new AbortController(); | ||
this.pr = new Promise((_,rej)=>this.rej = rej); | ||
this.pr.catch(_=>1); // silence unhandled rejection warnings | ||
// silence unhandled rejection warnings | ||
this.pr.catch(_=>1); | ||
} | ||
} | ||
class cancelToken extends AbortController { | ||
constructor() { | ||
super(); | ||
this.signal = new cancelSignal(); | ||
} | ||
abort(reason) { | ||
super.abort(); | ||
this.signal.rej(reason); | ||
this.rej(reason); | ||
this.controller.abort(); | ||
} | ||
get signal() { | ||
this.controller.signal.pr = this.pr; | ||
return this.controller.signal; | ||
} | ||
} | ||
@@ -37,5 +35,5 @@ | ||
function CAF(generatorFn) { | ||
return function instance(cancelToken,...args){ | ||
var { it, pr } = _runner.call(this,generatorFn,cancelToken,...args); | ||
var cancel = cancelToken.pr.catch(function onCancel(reason){ | ||
return function instance(signal,...args){ | ||
var { it, success } = _runner.call(this,generatorFn,signal,...args); | ||
var cancelation = signal.pr.catch(function onCancel(reason){ | ||
try { | ||
@@ -45,7 +43,7 @@ var ret = it.return(); | ||
} | ||
finally { it = pr = cancel = null; } | ||
finally { it = success = cancelation = null; } | ||
}); | ||
var race = Promise.race([ pr, cancel ]); | ||
race.catch(_=>1); // silence unhandled rejection warnings | ||
return race; | ||
var completion = Promise.race([ success, cancelation ]); | ||
completion.catch(_=>1); // silence unhandled rejection warnings | ||
return completion; | ||
}; | ||
@@ -63,3 +61,3 @@ } | ||
it, | ||
pr: Promise.resolve( | ||
success: Promise.resolve( | ||
(function handleNext(value){ | ||
@@ -66,0 +64,0 @@ // run to the next yielded value |
@@ -39,5 +39,5 @@ "use strict"; | ||
QUnit.test( "CAF() + this + parameters + return", async function test(assert){ | ||
function *checkParameters(cancelToken,a,b,...args) { | ||
function *checkParameters(signal,a,b,...args) { | ||
assert.step(this.x); | ||
assert.step(String(cancelToken === token.signal)); | ||
assert.step(String(signal === token.signal)); | ||
assert.step(a); | ||
@@ -77,3 +77,3 @@ assert.step(b); | ||
QUnit.test( "cancelation + rejection", async function test(assert){ | ||
function *main(cancelToken,ms) { | ||
function *main(signal,ms) { | ||
for (let i = 0; i < 5; i++) { | ||
@@ -114,3 +114,3 @@ assert.step(`step: ${i}`); | ||
QUnit.test( "cancelation + finally", async function test(assert){ | ||
function *main(cancelToken,ms) { | ||
function *main(signal,ms) { | ||
try { | ||
@@ -156,3 +156,3 @@ for (let i = 0; i < 5; i++) { | ||
QUnit.test( "cascading cancelation", async function test(assert){ | ||
function *main(cancelToken,ms) { | ||
function *main(signal,ms) { | ||
try { | ||
@@ -162,6 +162,6 @@ assert.step("main: 1"); | ||
var x = yield secondary(cancelToken,ms,2); | ||
var x = yield secondary(signal,ms,2); | ||
assert.step(`main: ${x}`); | ||
x = yield secondary(cancelToken,ms,3); | ||
x = yield secondary(signal,ms,3); | ||
assert.step("shouldn't happen"); | ||
@@ -174,3 +174,3 @@ } | ||
function *secondary(cancelToken,ms,v) { | ||
function *secondary(signal,ms,v) { | ||
try { | ||
@@ -177,0 +177,0 @@ assert.step(`secondary: ${v}`); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
35052
326
658