async_job 0.2.0
async_job: ^0.2.0 copied to clipboard
Cooperative cancellation, child tasks, resource cleanup and explicit outcomes for asynchronous Dart code. Pure Dart.
0.2.0 #
-
Breaking:
ctx.run(child)returnsFuture<T>instead of the child's handle. Useawait ctx.run(child)to await its value, children and cleanup; successful completion checks the parent's cancellation before continuing. Keep the originalchildfor cancellation or outcome inspection. Handle the returned future's errors, or use.ignore()for an intentional concurrent start whose result is unused. Start validation still throws synchronously.ctx.eachcontinues to return its child handle immediately. -
Add protected
JobContextBase.startChildfor domain adoption and start checks shared byrunandeach, without observing the child's result. -
Add
Job.thenwith a separate context per continuation, forward outcome propagation and cancellation in both directions. Cancelling the tail waits for unfinished predecessors and their cleanup.ChainCancelReasonretains each adjacent cancellation incause. Continuations are core root jobs with their own optional observer, independent of domain queues. -
Breaking:
ctx.each(stream, (child, event) { ... })returns a childJob<void>immediately. Await.valuefor a throwing result, inspect.donefor the outcome, or call.cancel()to stop it separately. The callback receives the child's context. The parent waits for the child even when its body does not await it, and observers see the child. -
Cancelling
eachimmediately removes the subscription and stops event delivery, then waits for the running callback before child completion and cleanup. Use child context checkpoints to respond to cancellation; a plainawaitcannot be interrupted. Source cleanup from subscription cancellation is still not awaited. -
Breaking:
eachis aJobContextmethod; theJobStreamextension is removed.JobContextBase.createEachJobis a protected factory for domain engines. As withrun,eachis rejected fromunattendedand after the parent body ends. -
Breaking: reasons are extensible classes:
ManualCancelReason,ParentCancelReasonandHandlerCancelReason. Replace named constants with constructors and inspect types instead of comparing names.CancelReasonis abstract; subclasses can carry arbitrary data. -
job.cancel(reason: reason)accepts a custom reason, preserved by identity in callbacks and outcomes. A body throwingCancelled.bynow preserves its explicit reason as well. -
Parent cancellation and a child's cancellation escaping the body retain the source
CancelledinParentCancelReason.causeandHandlerCancelReason.causerespectively. -
Breaking: replace the
Job.whenCancelledfuture withjob.whenCancelled((cancelled) { ... }). The callback receives the fullCancelledand runs synchronously; registering after cancellation calls it immediately. The returned function unregisters the callback. -
Release unused cancellation listeners when a job finishes without cancellation. Registration does not observe a failed outcome.
-
Route synchronous listener errors through
onError, or the creation zone without an observer, as forctx.onCancel. Async callbacks are not awaited.
0.1.0 #
Initial release. The job kernel taken out of solo before its own
first release.
Job<T>: a cancellableFuturewith an outcome, children and a cooperative cancellation. Created byJob(body), which starts on the next microtask, or byJob.deferred(body), which waits forstart().Outcome<T>:Done,Failed,Cancelledwith an openCancelReasonand a publicCancelled.byfor engines built on this one.- A waiting family that says what a cancellation does to a call:
ctx.waitends the waiting and not the work,ctx.joinwaits for all of the action and gives up afterwards,ctx.uncancellableholds the cancellation until the step is over,ctx.unattendeddoes not wait at all and hands the work to the engine,ctx.onCancelhands the cancellation to whatever can really stop, andctx.checkgives up where there is no call to wrap.ctx.eachfollows a stream for as long as the job lives, waiting for an asynchronousonDataand taking the subscription with it whatever the outcome. - A cleanup stack:
ctx.onDisposereleases whatever the outcome,ctx.onDiscardonly when the value reaches nobody, andwaitandjointake the same two asdisposeanddiscardfor the value they hand over. The engine unwinds the stack after the children and before the outcome, waiting for every disposer;ctx.disown(value)takes a registration back when the body hands the value over itself. - Children through
ctx.run: the parent finishes after them, a cancellation cascades, and a child that is not cancellable refuses it. JobObserverwithonStart,onFinish,onErrorandonLog; a child inherits the parent's. AFailedoutcome nobody observed goes to the zone that created the job, and so does an error with nowhere else to go when there is no observer — but never aCancelled: a cancellation is a decision somebody made, not a failure, and the observer is the only place it is heard.ctx.unattended(action)for work the body starts and does not wait for: it runs in an error zone of its own, and whatever it leaves uncaught — now, or long after the job is over — reachesonErrorinstead of the process.ctx.runandctx.uncancellableare refused from inside it, and a job created in there reports to the zone the body runs in, not to the observer of the job that started the work.JobBase<T>andJobContextBasefor an engine of a domain to subclass: the protected surface, the virtual checkpointcheck(), and the hooksstarted(),finished()andadoptedBy(). The protected surface also carriesreportToZone, for a domain whose own route for an error with nowhere to go ends with nobody — it keeps aCancelledout of the zone exactly as the core does, so a domain does not write that rule again — andthrowIfUnattended, for a member of its context that must not be called from unattended work.