Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
forked fromgetify/CAF

Cancelable Async Flows (CAF)

License

NotificationsYou must be signed in to change notification settings

divinesweet/CAF

 
 

Repository files navigation

Build Statusnpm ModuleCoverage Status

CAF (/ˈkahf/) is a wrapper forfunction* generators that treats them likeasync functions, but with support for external cancellation via tokens. In this way, you can express flows of synchronous-looking asynchronous logic that are still cancelable (CancelableAsyncFlows).

Also included isCAG(..), for alternately wrappingfunction* generators to emulate ES2018 async-generators (async function *).

Environment Support

This library uses ES2018 features. If you need to support environments prior to ES2018, transpile it first (with Babel, etc).

At A Glance

CAF (CancelableAsyncFlows) wraps afunction* generator so it looks and behaves like anasync function, but that can be externally canceled using a cancellation token:

vartoken=newCAF.cancelToken();// wrap a generator to make it look like a normal async// function that when called, returns a promise.varmain=CAF(function*main(signal,url){varresp=yieldajax(url);// want to be able to cancel so we never get here?!?console.log(resp);returnresp;});// run the wrapped async-looking function, listen to its// returned promisemain(token.signal,"http://some.tld/other").then(onResponse,onCancelOrError);// only wait 5 seconds for the ajax request!setTimeout(functiononElapsed(){token.abort("Request took too long!");},5000);

Create a cancellation token (vianew CAF.cancelToken()) to pass into your wrappedfunction* generator, and then if you cancel the token, thefunction* generator will abort itself immediately, even if it's presently waiting on a promise to resolve.

The generator receives the cancellation token'ssignal, so from inside it you can call anotherfunction* generator viaCAF and pass along that sharedsignal. In this way, a single cancellation signal can cascade across and cancel all theCAF-wrapped functions in a chain of execution:

vartoken=newCAF.cancelToken();varone=CAF(function*one(signal,v){returnyieldtwo(signal,v);});vartwo=CAF(function*two(signal,v){returnyieldthree(signal,v);});varthree=CAF(function*three(signal,v){returnyieldajax(`http://some.tld/?v=${v}`);});one(token.signal,42);// only wait 5 seconds for the request!setTimeout(functiononElapsed(){token.abort("Request took too long!");},5000);

In this snippet,one(..) calls and waits ontwo(..),two(..) calls and waits onthree(..), andthree(..) calls and waits onajax(..). Because the same cancellation token is used for the 3 generators, iftoken.abort() is executed while they're all still paused, they will all immediately abort.

Note: The cancellation token has no effect on the actualajax(..) call itself here, since that utility ostensibly doesn't provide cancellation capability; the Ajax request itself would still run to its completion (or error or whatever). We've only canceled theone(..),two(..), andthree(..) functions that were waiting to process its response. SeeAbortController(..) andManual Cancellation Signal Handling below for addressing this limitation.

CAG: Cancelable AsyncFlowsGenerators

ES2018 added "async generators", which is a pairing ofasync function andfunction* -- so you can useawait andyield in the same function,await for unwrapping a promise, andyield for pushing a value out. An async-generator (async function * f(..) { .. }), like regular iterators, is designed to be sequentially iterated, but using the "async iteration" protocol.

For example, in ES2018:

asyncfunction*stuff(urls){for(leturlofurls){letresp=awaitfetch(url);// await a promiseyieldresp.json();// yield a value (even a promise for a value)}}// async-iteration loopforawait(letvofstuff(assetURLs)){console.log(v);}

In the same way thatCAF(..) emulates anasync..await function with afunction* generator, theCAG(..) utility emulates an async-generator with a normalfunction* generator. You can cancel an async-iteration early (even if it's currently waiting internally on a promise) with a cancellation token.

You can also synchronously force-close an async-iterator by calling thereturn(..) on the iterator. With native async-iterators,return(..) is not actually synchronous, butCAG(..) patches this to allow synchronous closing.

Instead ofyielding a promise the way you do withCAF(..), you use a providedpwait(..) function withyield, likeyield pwait(somePromise). This allows ayield ..value.. expression for pushing out a value through the iterator, as opposed toyield pwait(..value..) to locally wait for the promise to resolve. To emulate ayield await ..value.. expression (common in async-generators), you use twoyields together:yield yield pwait(..value..).

For example:

// NOTE: this is CAG(..), not to be confused with CAF(..)varstuff=CAG(function*stuff({ signal, pwait},urls){for(leturlofurls){letresp=yieldpwait(fetch(url,{ signal}));// await a promiseyieldresp.json();// yield a value (even a promise for a value)}});vartimeout=CAF.timeout(5000,"That's enough results!");varit=stuff(timeout,assetURLs);cancelBtn.addEventListener("click",()=>it.return("Stop!"),false);// async-iteration loopforawait(letvofit){console.log(v);}

In this snippet, thestuff(..) async-iteration can either be canceled if the 5-second timeout expires before iteration is complete, or the click of the cancel button can force-close the iterator early. The difference between them is that token cancellation would result in an exception bubbling out (to the consuming loop), whereas callingreturn(..) will simply cleanly close the iterator (and halt the loop) with no exception.

Background/Motivation

Generally speaking, anasync function and afunction* generator (driven with agenerator-runner) look very similar. For that reason, most people just prefer theasync function form since it's a little nicer syntax and doesn't require a library for the runner.

However, there are limitations toasync functions that come from having the syntax and engine make implicit assumptions that otherwise would have been handled by afunction* generator runner.

One unfortunate limitation is that anasync 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 potential cancellation point. This is ugly and error-prone.

function* generators by contrast can be aborted at any time, using the iterator'sreturn(..) method and/or by just not resuming the generator iterator instance withnext(). But the downside of usingfunction* generators is either needing a runner utility or the repetitive boilerplate of handling the iterator manually.

CAF provides a useful compromise: afunction* generator that can be called like a normalasync function, but which supports a cancellation token.

TheCAF(..) utility wraps afunction* generator with a normal promise-returing function, just as if it was anasync function. Other than minor syntactic aesthetics, the major observable difference is that aCAF-wrapped function must be provided a cancellation token'ssignal as its first argument, with any other arguments passed subsequent, as desired.

By contrast, theCAG(..) utility wraps afunction* generator as an ES2018 async-generator (async function *) that respects the native async-iteration protocol. Instead ofawait, you useyield pwait(..) in these emulated async-generators.

Overview

In the following snippet, the two functions are essentially equivalent;one(..) is an actualasync function, whereastwo(..) is a wrapper around a generator, but will behave like an async function in that it also returns a promise:

asyncfunctionone(v){awaitdelay(100);returnv*2;}vartwo=CAF(function*two(signal,v){yielddelay(100);returnv*2;});

Bothone(..) andtwo(..) can be called directly with argument(s), and both return a promise for their completion:

one(21).then(console.log,console.error);// 42vartoken=newCAF.cancelToken();two(token.signal,21).then(console.log,console.error);// 42

Iftoken.abort(..) is executed whiletwo(..) is still running, thesignal's promise will be rejected. If you pass a cancellation reason (any value, but typically a string) totoken.abort(..), that will be the promise rejection reason:

two(token,21).then(console.log,console.error);// Took too long!token.abort("Took too long!");

Delays & Timeouts

One of the most common use-cases for cancellation of an async task is because too much time passes and a timeout threshold is passed.

As shown earlier, you can implement that sort of logic with acancelToken() instance and a manual call to the environment'ssetTimeout(..). However, there are some subtle but important downsides to doing this kind of thing manually. These downsides are harder to spot in the browser, but are more obvious in Node.js

Consider this code:

functiondelay(ms){returnnewPromise(functionc(res){setTimeout(res,ms);});}vartoken=newCAF.cancelToken();varmain=CAF(function*main(signal,ms){yielddelay(ms);console.log("All done!");});main(token.signal,100);// only wait 5 seconds for the request!delay(5000).then(functiononElapsed(){token.abort("Request took too long!");});

Themain(..) function delays for100ms and then completes. But there's no logic that clears the timeout set fromdelay( 5000 ), so it will continue to hold pending until that amount of time expires.

Of course, thetoken.abort(..) call at that point is moot, and is thus silently ignored. But the problem is the timer still running, which keeps a Node.js process alive even if the rest of the program has completed. The symptoms of this would be running a Node.js program from the command line and observing it "hang" for a bit at the end instead of exiting right away. Try the above code to see this in action.

There's two complications that make avoiding this downside tricky:

  1. Thedelay(..) helper shown, which is a promisified version ofsetTimeout(..), is basically what you can produce by usingNode.js'sutil.promisify(..) againstsetTimeout(..). However, that timer itself is not cancelable. You can't access the timer handle (return value fromsetTimeout(..)) to callclearTimeout(..) on it. So, you can't stop the timer early even if you wanted to.

  2. If instead you set up your own timer externally, you need to keep track of the timer's handle so you can callclearTimeout(..) if the async task completes successfully before the timeout expires. This is manual and error-prone, as it's far too easy to forget.

Instead of inventing solutions to these problems,CAF provides two utilities for managing cancelable delays and timeout cancellations:CAF.delay(..) andCAF.timeout(..).

CAF.delay(..)

What we need is a promisifiedsetTimeout(..), likedelay(..) we saw in the previous section, but that can still be canceled.CAF.delay(..) provides us such functionality:

vardiscardTimeout=newCAF.cancelToken();// a promise that waits 5 secondsCAF.delay(discardTimeout.signal,5000).then(functiononElapsed(msg){// msg: "delayed: 5000"},functiononInterrupted(reason){// reason: "delay (5000) interrupted"});

As you can see,CAF.delay(..) receives a cancellation token signal to cancel the timeout early when needed. If you need to cancel the timeout early, abort the cancellation token:

discardTimeout.abort();// cancel the `CAF.delay()` timeout

The promise returned fromCAF.delay(..) is fulfilled if the full time amount elapses, with a message such as"delayed: 5000". But if the timeout is aborted via the cancellation token, the promise is rejected with a reason like"delay (5000) interrupted".

Passing the cancellation token toCAF.delay(..) is optional; if omitted,CAF.delay(..) works just like a regular promisifiedsetTimeout(..):

// promise that waits 200 msCAF.delay( 200 ).then( function onElapsed(){    console.log( "Some time went by!" );} );

CAF.timeout(..)

WhileCAF.delay(..) provides a cancelable timeout promise, it's still overly manual to connect the dots between aCAF-wrapped function and the timeout-abort process.CAF providesCAF.timeout(..) to streamline this common use-case:

vartimeoutToken=CAF.timeout(5000,"Took too long!");varmain=CAF(function*main(signal,ms){yieldCAF.delay(signal,ms);console.log("All done!");});main(timeoutToken,100);// NOTE: pass the whole token, not just the .signal !!

CAF.timeout(..) creates an instance ofcancellationToken(..) that's set toabort() after the specified amount of time, optionally using the cancellation reason you provide.

Note that you should pass the fulltimeoutToken token to theCAF-wrapped function (main(..)), instead of just passingtimeoutToken.signal. By doing so,CAF wires the token and theCAF-wrapped function together, so that each one stops the other, whichever one happens first. No more hanging timeouts!

Also note thatmain(..) still receives just thesignal as its first argument, which is suitable to pass along to other cancelable async functions, such asCAF.delay(..) as shown.

timeoutToken is a regular cancellation token as created byCAF.cancelToken(). As such, you can callabort(..) on it directly, if necessary. You can also accesstimeoutToken.signal to access its signal, andtimeoutToken.signal.pr to access the promise that's rejected when the signal is aborted.

finally { .. }

finally clauses are often attached to atry { .. } block wrapped around the entire body of a function, even if there's nocatch clause defined. The most common use of this pattern is to define some clean-up operations to be performed after the function is finished, whether that was from normal completion or an early termination (such as uncaught exceptions, or cancellation).

Canceling aCAF-wrappedfunction* generator that is paused causes it to abort right away, but if there's a pendingfinally {..} clause, it will always still have a chance to run.

vartoken=newCAF.cancelToken();varmain=CAF(function*main(signal,url){try{returnyieldajax(url);}finally{// perform some clean-up operations}});main(token.signal,"http://some.tld/other").catch(console.log);// 42 <-- not "Stopped!"token.abort("Stopped!");

Moreover, areturn of any non-undefined value in a pendingfinally {..} clause will override the promise rejection reason:

vartoken=newCAF.cancelToken();varmain=CAF(function*main(signal,url){try{returnyieldajax(url);}finally{return42;}});main(token.signal,"http://some.tld/other").catch(console.log);// 42 <-- not "Stopped!"token.abort("Stopped!");

Whatever value is passed toabort(..), if any, is normally set as the overall promise rejection reason. But in this case,return 42 overrides the"Stopped!" rejection reason.

signal.aborted andsignal.reason

StandardAbortSignal instances have anaborted boolean property that's set totrue once the signal is aborted. Once aborted, CAF also extends signals to also include areason property with the value (if any) passed to theAbortController'sabort(..) call.

Note: Passing areason to anabort(..) call on a cancellation token is CAF-specific; callingabort() on a directAbortController instance will ignore any passed-in value, though all the rest of the CAF cancellation mechanism still works. See the followingAbortController section for more information.

By checking thesignal.aborted flag in afinally clause, you can determine whether the function was canceled, and then additionally access thesignal.reason to determine more specific context information about why the cancellation occurred. This allows you to perform different clean-up operations depending on cancellation or normal completion:

vartoken=newCAF.cancelToken();varmain=CAF(function*main(signal,url){try{returnyieldajax(url);}finally{if(signal.aborted){console.log(`Cancellation reason:${signal.reason}`);// perform cancellation-specific clean-up operations}else{// perform non-cancellation clean-up operations}}});main(token.signal,"http://some.tld/other");// Cancellation reason: Stopped!token.abort("Stopped!");

Memory Cleanup Withdiscard()

A cancellation token from CAF includes adiscard() method which can be called at any time to fully unset any internal state in the token to allow proper GC (garbage collection) of any attached resources.

When you are sure you're fully done with a cancellation token, it's a good idea to calldiscard() on it, and then unset the variable holding that reference:

vartoken=newCAF.cancelToken();// latertoken.discard();token=null;

Once a token has beendiscard()ed, no further calls toabort(..) will be effective -- they will silently be ignored.

AbortController(..)

CAF.cancelToken(..) instantiatesAbortController, the DOM standard for canceling/aborting operations likefetch(..) calls. As such, aCAF cancellation token'ssignal can be passed directly to a DOM method likefetch(..) to control its cancellation:

vartoken=newCAF.cancelToken();varmain=CAF(function*main(signal,url){varresp=yieldfetch(url,{ signal});console.log(resp);returnresp;});main(token.signal,"http://some.tld/other").catch(console.log);// "Stopped!"token.abort("Stopped!");

CAF.cancelToken(..) can optionally receive an already instantiatedAbortController, though there's rarely a reason to do so:

varac=newAbortController();vartoken=newCAF.cancelToken(ac);

Also, if you pass a rawAbortController instance into aCAF-wrapped function, it's automatically wrapped into aCAF.cancelToken(..) instance:

varmain=CAF(function*main(signal,url){varresp=yieldfetch(url,{ signal});console.log(resp);returnresp;});varac=newAbortController();main(ac,"http://some.tld/other").catch(()=>console.log("Stopped!"));// "Stopped!"token.abort();

AbortController() Polyfill

IfAbortController is not defined in the environment, use thispolyfill to define a compatible stand-in. The polyfill can be found in thedist/ directory alongsidecaf.js.

Note: The polyfill is automatically loaded (in theglobal namespace) whenCAF is used is Node.

Just be aware that if an environment needs the polyfill,fetch(..) and other such APIs won't know aboutAbortController so they won't recognize or respond to it. They won't break in its presence; they'll just ignore it.

Manual Cancellation Signal Handling

Even if you aren't calling a cancellation signal-aware utility (likefetch(..)), you can still manually respond to the cancellationsignal via its attached promise:

vartoken=newCAF.cancelToken();varmain=CAF(function*main(signal,url){// listen to the signal's promise rejection directlysignal.pr.catch(reason=>{// reason == "Stopped!"});varresp=yieldajax(url);console.log(resp);returnresp;});main(token.signal,"http://some.tld/other").catch(console.log);// "Stopped!"token.abort("Stopped!");

Note: Thecatch(..) handler inside ofmain(..) will still run, even thoughmain(..) itself will be aborted at its waitingyield statement. If there was a way to manually cancel theajax(..) call, that code should be placed in thecatch(..) handler.

Even if you aren't running aCAF-wrapped function, you can still respond to the cancellationsignal's promise manually to affect flow control:

vartoken=newCAF.cancelToken();// normal async function, not CAF-wrappedasyncfunctionmain(signal,url){try{varresp=awaitPromise.race([ajax(url),signal.pr// listening to the cancellation]);// this won't run if `signal.pr` rejectsconsole.log(resp);returnresp;}catch(err){// err == "Stopped!"}}main(token.signal,"http://some.tld/other").catch(console.log);// "Stopped!"token.abort("Stopped!");

Note: As discussed earlier, theajax(..) call itself is not cancellation-aware, and is thus not being canceled here. But weare ending our waiting on theajax(..) call. Whensignal.pr wins thePromise.race(..) race and creates an exception from its promise rejection, flow control jumps straight to thecatch (err) { .. } clause.

Signal Combinators

You may want to combine two or more signals, similar to how you combine promises withPromise.race(..) andPromise.all(..).CAF provides two corresponding helpers for this purpose:

vartimeout=CAF.timeout(5000,"Took too long!");varcanceled=newCAF.cancelToken();varexit=newAbortController();varanySignal=CAF.signalRace([timeout.signal,canceled.signal,exit.signal]);varallSignals=CAF.signalAll([timeout.signal,canceled.signal,exit.signal]);main(anySignal,"http://some.tld/other");// ormain(allSignals,"http://some.tld/other");

CAF.signalRace(..) expects an array of one or more signals, and returns a new signal (anySignal) that will fire as soon as any of the constituent signals have fired.

CAF.signalAll(..) expects an array of one or more signals, and returns a new signal (allSignals) that will fire only once all of the constituent signals have fired.

Warning: This pattern (combining signals) has a potential downside.CAF typically cleans up timer-based cancel tokens to make sure resources aren't being wasted and programs aren't hanging with open timer handles. But in this pattern,signalRace(..) /signalAll(..) only receive reference(s) to the signal(s), not the cancel tokens themselves, so it cannot do the manual cleanup. In the above example, you should manually clean up the 5000ms timer by callingtimeout.abort() if the operation finishes before that timeout has fired the cancellation.

Beware Of Token Reuse

Beware of creating a single cancellation token that is reused for separate chains of function calls. Unexpected results are likely, and they can be extremely difficult to debug.

As illustrated earlier, it's totally OK and intended that a single cancellation tokensignal be shared across all the functions inone chain of calls (A ->B ->C). But reusing the same tokenacross two or more chains of calls (A ->B ->CandD ->E ->F) is asking for trouble.

Imagine a scenario where you make two separatefetch(..) calls, one after the other, and the second one runs too long so you cancel it via a timeout:

varone=CAF(function*one(signal){signal.pr.catch(reason=>{console.log(`one:${reason}`);});returnyieldfetch("http://some.tld/",{signal});});vartwo=CAF(function*two(signal,v){signal.pr.catch(reason=>{console.log(`two:${reason}`);});returnyieldfetch(`http://other.tld/?v=${v}`,{signal});});vartoken=CAF.cancelToken();one(token.signal).then(function(v){// only wait 3 seconds for this requestsetTimeout(function(){token.abort("Second response too slow.");},3000);returntwo(token.signal,v);}).then(console.log,console.error);// one: Second response too slow.   <-- Oops!// two: Second response too slow.// Second response too slow.

When you calltoken.abort(..) to cancel the secondfetch(..) call intwo(..), thesignal.pr.catch(..) handler inone(..) still gets called, even thoughone(..) is already finished. That's why"one: Second response too slow." prints unexpectedly.

The underlying gotcha is that a cancellation token'ssignal has a singlepr promise associated with it, and there's no way to reset a promise or "unregister"then(..) /catch(..) handlers attached to it once you don't need them anymore. So if you reuse the token, you're reusing thepr promise, and all registered promise handlers will be fired, even old ones you likely don't intend.

The above snippet illustrates this problem withsignal.pr.catch(..), but any of the other ways of listening to a promise -- such asyield /await,Promise.all(..) /Promise.race(..), etc -- are all susceptible to the unexpected behavior.

The safe and proper approach is to always create a new cancellation token for each chain ofCAF-wrapped function calls. For good measure, always unset any references to a token once it's no longer needed, and make sure to calldiscard(); thus, you won't accidentally reuse the token, and the JS engine can properly GC (garbage collect) it.

CAG: Emulating Async Generators

WhereCAF(..) emulates a promise-returningasync function using a generator,CAG(..) is provided to emulate an async-iterator returning async-generator (async function*).

Async iteration is similar to streams (or primitive observables), where the values are consumed asynchronously (typically using an ES2018for await (..) loop):

forawait(letvofsomeAsyncGenerator()){// ..}// or:varit=someAsyncGenerator();forawait(letvofit){// ..}

For all the same reasons thatasync functions being non-cancelable is troublesome, async-generators are similarly susceptible. An async-generator can be "stuck"awaiting internally on a promise, and the outer consuming code cannot do anything to force it to stop.

That's whyCAG(..) is useful:

// instead of:asyncfunction*stuff(urls){for(leturlofurls){letresp=awaitfetch(url);// await a promiseyieldresp.json();// yield a value (even a promise for a value)}}// do this:varstuff=CAG(function*stuff({ signal, pwait},urls){for(leturlofurls){letresp=yieldpwait(fetch(url,{ signal}));// await a promiseyieldresp.json();// yield a value (even a promise for a value)}});

LikeCAF(..), functions wrapped byCAG(..) expect to receive a special value in their first parameter position. Here, the object is destructured to reveal it contains both the cancellation-tokensignal (as withCAF(..)) and thepwait(..) function, which enables emulating localawait ..promise.. expressions asyield pwait(..promise..).

The return from aCAG(..) wrapped function is an async-iterator (exactly as if a real native async-generator had been invoked). As withCAF(..) values, the first argument passed should always be the mandatory cancellation token (or its signal):

varstuff=CAG(function*stuff(..){..});vartimeout=CAF.timeout(5000,"Took too long.");varit=stuff(timeout);

The returned async-iterator (it above) can be iterated manually withit.next(..) calls -- each returns a promise for an iterator-result -- or more preferably with an ES2018for await (..) loop:

vartimeout=CAF.timeout(5000,"Took too long.");varit=stuff(timeout);var{ value, done}=awaitit.next();// ..do that repeatedly..// or preferably:forawait(letvalueofit){// ..}

In addition to being able toabort(..) the cancellation token passed into aCAG(..)-wrapped generator, async-iterators also can be closed forcibly by calling theirreturn(..) method.

vartimeout=CAF.timeout(5000,"Took too long.");varit=stuff(timeout);// later (e.g. in a timer or event handler):it.return("all done");// Promise<{ value: "all done", done: true }>

Typically, thereturn(..) call on an async-iterator (from an async-generator) will have "wait" for the attached async-generator to be "ready" to be closed -- in case anawait promise expression is currently pending. This means you cannot actually synchronously force-close them. But sinceCAG(..) emulates async-generators with regular sync-generators, this nuance is "fixed". For consistency,return(..) still returns a Promise, but it's an already-resolved promise with the associated iterator-result.

CAG(..)-wrapped functions also follow these behaviors ofCAF(..)-wrapped functions:

  • Aborting the cancellation token results in an exception (which can be trapped bytry..catch) propagating out from the most recentfor await (..) (orit.next(..)) consumption point.

  • Thereason provided when aborting a cancellation token is (by default) set as the exception that propagates out. This can be overriden by areturn .. statement in afinally clause of the wrapped generator function.

npm Package

To install this package fromnpm:

npm install caf

And to require it in a node script:

varCAF=require("caf");varCAG=require("caf/cag");

As of version 11.0.0, the package is also available as an ES Module, and can be imported as so:

import{CAF,CAG}from"caf/esm";// or:importCAFfrom"caf/esm/caf";importCAGfrom"caf/esm/cag";

Builds

Build Statusnpm Module

The distribution library file (dist/caf.js) comes pre-built with the npm package distribution, so you shouldn't need to rebuild it under normal circumstances.

However, if you download this repository via Git:

  1. The included build utility (scripts/build-core.js) builds (and minifies)dist/caf.js from source.

  2. To install the build and test dependencies, runnpm install from the project root directory.

  3. To manually run the build utility with npm:

    npm run build
  4. To run the build utility directly without npm:

    node scripts/build-core.js

Tests

A test suite is included in this repository, as well as the npm package distribution. The default test behavior runs the test suite using the files insrc/.

  1. The tests are run with QUnit.

  2. You can run the tests in a browser by opening uptests/index.html.

  3. To run the test utility with npm:

    npm test

    Other npm test scripts:

    • npm run test:dist will run the test suite againstdist/caf.js instead of the default ofsrc/caf.js.

    • npm run test:package will run the test suite as if the package had just been installed via npm. This ensurespackage.json:main properly references the correct file for inclusion.

    • npm run test:all will run all three modes of the test suite.

  4. To run the test utility directly without npm:

    node scripts/node-tests.js

Test Coverage

Coverage Status

If you haveNYC (Istanbul) already installed on your system (requires v14.1+), you can use it to check the test coverage:

npm run coverage

Then open upcoverage/lcov-report/index.html in a browser to view the report.

Note: The npm scriptcoverage:report is only intended for use by project maintainers. It sends coverage reports toCoveralls.

License

All code and documentation are (c) 2019-2020 Kyle Simpson and released under theMIT License. A copy of the MIT Licenseis also included.

About

Cancelable Async Flows (CAF)

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript98.9%
  • HTML1.1%

[8]ページ先頭

©2009-2025 Movatter.jp