{"id":13746922,"url":"https://github.com/mitranim/posterus","last_synced_at":"2025-04-04T21:08:53.600Z","repository":{"id":52449835,"uuid":"91579170","full_name":"mitranim/posterus","owner":"mitranim","description":"Composable async primitives with cancelation, control over scheduling, and coroutines. Superior replacement for JS Promises.","archived":false,"fork":false,"pushed_at":"2021-05-07T15:32:25.000Z","size":179,"stargazers_count":548,"open_issues_count":0,"forks_count":19,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-01T21:04:29.266Z","etag":null,"topics":["abortable","async","async-await","cancelable","composable","coroutine","future","promise","scheduling"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mitranim.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-17T13:24:40.000Z","updated_at":"2025-02-13T01:14:21.000Z","dependencies_parsed_at":"2022-08-18T23:50:52.535Z","dependency_job_id":null,"html_url":"https://github.com/mitranim/posterus","commit_stats":null,"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Fposterus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Fposterus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Fposterus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitranim%2Fposterus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mitranim","download_url":"https://codeload.github.com/mitranim/posterus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247249527,"owners_count":20908212,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abortable","async","async-await","cancelable","composable","coroutine","future","promise","scheduling"],"created_at":"2024-08-03T06:01:05.454Z","updated_at":"2025-04-04T21:08:53.577Z","avatar_url":"https://github.com/mitranim.png","language":"JavaScript","readme":"## Overview\n\nSuperior replacement for [JS promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises).\n\nSimilarities with promises:\n\n* Supports mapping, flatmapping, grouping, etc.\n* Supports coroutines (via generators, see `./fiber.mjs`).\n\nMain differences from promises:\n\n* Supports cancelation.\n* Mostly synchronous.\n* Exposes its [scheduler](#async), allowing to opt into asynchrony, and _opt out_ by flushing pending tasks on demand.\n* Supports \"errbacks\": single callback that receives \"err, val\".\n* Constructor doesn't require a callback.\n* Mapping mutates the task instead of allocating new instances.\n* Doesn't store results.\n* Dramatically simpler and faster.\n\nSmall (\u003c12 KiB unminified) and dependency-free. Usable as a native JS module.\n\nOptionally supports coroutines/fibers (\u003c2 KiB unminified). Replacement for async/await, with implicit ownership and cancelation of in-progress work. See [API (`fiber.mjs`)](#api-fibermjs).\n\nConvertible [to](#tasktopromise) and [from](#frompromisepromise) promises.\n\n## TOC\n\n* [Why](#why)\n  * [Why cancelation?](#why-cancelation)\n  * [Why not extend standard promises?](#why-not-extend-standard-promises)\n  * [Unicast vs Broadcast](#unicast-vs-broadcast)\n  * [Why not Rx observables?](#why-not-rx-observables)\n  * [Why not Bluebird?](#why-not-bluebird)\n* [Usage](#usage)\n* [API](#api)\n  * [`Task()`](#task)\n    * [`task.isDone()`](#taskisdone)\n    * [`task.done(err, val)`](#taskdoneerr-val)\n    * [`task.map(fun)`](#taskmapfun)\n    * [`task.mapErr(fun)`](#taskmaperrfun)\n    * [`task.mapVal(fun)`](#taskmapvalfun)\n    * [`task.finally(fun)`](#taskfinallyfun)\n    * [`task.onDeinit(fun)`](#taskondeinitfun)\n    * [`task.deinit()`](#taskdeinit)\n    * [`task.toPromise()`](#tasktopromise)\n  * [`Scheduler()`](#scheduler)\n    * [`scheduler.push(task, err, val)`](#schedulerpushtask-err-val)\n    * [`scheduler.fromErr(err)`](#schedulerfromerrerr)\n    * [`scheduler.fromVal(val)`](#schedulerfromvalval)\n    * [`scheduler.tick()`](#schedulertick)\n  * [`AsyncTask()`](#asynctask)\n  * [`async`](#async)\n  * [`isTask(val)`](#istaskval)\n  * [`branch(task)`](#branchtask)\n  * [`all(list)`](#alllist)\n  * [`dictAll(dict)`](#dictalldict)\n  * [`race(list)`](#racelist)\n  * [`fromPromise(promise)`](#frompromisepromise)\n  * [`toTask(val)`](#totaskval)\n* [API (`fiber.mjs`)](#api-fibermjs)\n  * [`Fiber()`](#fiber)\n  * [`fiber(fun)`](#fiberfun)\n  * [`fiberAsync(fun)`](#fiberasyncfun)\n  * [`fromIter(iter)`](#fromiteriter)\n  * [`fromIterAsync(iter)`](#fromiterasynciter)\n* [Changelog](#changelog)\n\n## Why\n\n* Correct async programming requires cancelation.\n* `Promise` is crippled by lack of cancelation.\n* `Promise` is further mangled by mandatory asynchrony.\n* Replacing the broken model is better than trying to augment it.\n\n### Why cancelation?\n\nHumans and even programs change their minds all the time. Many behaviors we consider intuitive rely on some form of cancelation.\n\n* Start playing a video, then hit stop. Should it finish playing?\n\n* Click a web link, then immediately click another. Should it still load the first link?\n\n* Start uploading a file, then hit stop. Should it upload anyway?\n\n* Run an infinite loop. Should it hog a CPU core until you reboot the operating system?\n\n* Hit a button to launch nuclear missiles, immediately hit abort. Nuke another country anyway?\n\nWhat does it mean for the programmer?\n\nFirst, this mostly applies to user-driven programs. The concept of cancelation to a normal synchronous program is like the 4th spatial dimension to a human mind: equally incomprehensible.\n\nSynchronous code tends to be a sequence of blocking operations, with no room for changing one's mind. This makes it inherently unresponsive and therefore unfit for user-driven programs. Said programs end up using multithreading, event loops, and other inherently asynchronous techniques. The asynchrony is how you end up with abstractions like promises, responding to a fickle user is how you end up needing cancelation, and being responsive is how you're _able_ to cancel.\n\nSync and async programming are inherently complementary. For invididual operations, we tend to think in sequential terms. For systems, we tend to think in terms of events and reactions. Neither paradigm fully captures the needs of real-world programming. Most non-trivial systems end up with an asynchronous core, laced with the macaroni of small sequential programs that perform individual functions.\n\nJavaScript forces all programs to be asynchonous and responsive. Many of these programs don't need the asynchrony, don't respond to fickle agents, and could have been written in Python. Other programs need all of that.\n\nHere's more examples made easier by cancelation.\n\n#### 1. Race against timeout\n\nWith promises (broken):\n\n```js\nPromise.race([\n  after(100).then(() =\u003e {\n    console.log('running delayed operation')\n  }),\n  after(50).then(() =\u003e {throw Error('timeout')}),\n])\n\nfunction after(time) {\n  return new Promise(resolve =\u003e setTimeout(resolve, time))\n}\n```\n\nTimeout wins → delayed operation runs anyway. Is that what we wanted?\n\nNow, with cancelable tasks:\n\n```js\nimport * as p from 'posterus'\n\np.race([\n  after(100).mapVal(() =\u003e {\n    console.log('running delayed operation')\n  }),\n  after(50).mapVal(() =\u003e {throw Error('timeout')}),\n])\n\nfunction after(time) {\n  const task = new p.Task()\n  const cancel = timeout(time, task.done.bind(task))\n  task.onDeinit(cancel)\n  return task\n}\n\nfunction timeout(time, fun, ...args) {\n  return clearTimeout.bind(undefined, setTimeout(fun, time, ...args))\n}\n```\n\nTimeout wins → delayed operation doesn't run, and its timer is _actually_ canceled.\n\n#### 2. Race condition: updating page after network request\n\nSuppose we update search results on a webpage. The user types, we make requests and render the results. The input might be debounced; it doesn't matter.\n\nWith regular callbacks or promises:\n\n```js\nfunction onInput() {\n  httpRequest(searchParams).then(updateSearchResults)\n}\n```\n\nEventually, this happens:\n\n    request 1   start ----------------------------------- end\n    request 2            start ----------------- end\n\nAfter briefly rendering results from request 2, the page reverts to the results from request 1 that arrived out of order. Is that what we wanted?\n\nInstead, we could wrap HTTP requests in tasks, which support cancelation:\n\n```js\nfunction onInput() {\n  if (task) task.deinit()\n  task = httpRequest(searchParams).mapVal(updateSearchResults)\n}\n```\n\nNow there's no race.\n\nThis could have used `XMLHttpRequest` objects directly, but it shows why cancelation is a prerequisite for correct async programming.\n\n#### 3. Workarounds in the wild\n\nHow many libraries and applications have workarounds like this?\n\n```js\nlet canceled = false\nasyncOperation().then(() =\u003e {\n  if (!canceled) {/* do work */}\n})\nconst cancel = () =\u003e {canceled = true}\n```\n\nLive example from the Next.js source: https://github.com/zeit/next.js/blob/708193d2273afc7377df35c61f4eda022b040c05/lib/router/router.js#L298\n\nWorkarounds tend to indicate poor API design.\n\n### Why not extend standard promises?\n\n#### 1. You're already deviating from the spec\n\nCancelation support diverges from the spec by requiring additional methods. Not sure you should maintain the appearance of being spec-compliant when you're not. Using a different interface reduces the chances of confusion, while conversion [to](#tasktopromise) and [from](#frompromisepromise) promises makes interop easy.\n\n#### 2. Unicast is a better default than broadcast\n\nPromises are _broadcast_: they have multiple consumers. Posterus defaults\nto _unicast_: tasks have one consumer/owner, with broadcast as an option.\n\nBroadcast promises can support cancelation by using refcounting, like Bluebird. It works, but at the cost of compromises (pun intended) and edge cases. Defaulting to a unicast design lets you avoid them and greatly simplify the implementation.\n\nSee [Unicast vs Broadcast](#unicast-vs-broadcast) for a detailed explanation.\n\nPosterus provides broadcast as an [opt-in](#branchtask).\n\n#### 3. Annoyances in the standard\n\n##### Errbacks\n\nThis is a minor quibble, but I'm not satisfied with `then/catch`. It forces premature branching by splitting your code into multiple callbacks. Node-style \"errback\" continuation is often a better option. Adding this is yet another deviation. See [`task.map`](#taskmapfun).\n\n##### External Control\n\nHow many times have you seen code like this?\n\n```js\nlet resolve\nlet reject\nconst promise = new Promise((a, b) =\u003e {\n  resolve = a\n  reject = b\n})\nreturn {promise, resolve, reject}\n```\n\nOccasionally there's a need for a promise that is controlled \"externally\". The spec _goes out of its way_ to make it difficult.\n\nIn Posterus:\n\n```js\nimport * as p from 'posterus'\n\nconst task = new p.Task()\n```\n\nThat's it! Call `task.done()` to settle it.\n\n##### Error Flattening\n\n`Promise.reject(Promise.resolve(...))` passes the inner promise as the eventual result instead of flattening it. I find this counterintuitive.\n\n```js\nimport * as p from 'posterus'\n\nPromise.reject(Promise.resolve('\u003cresult\u003e')).catch(val =\u003e {\n  console.log(val)\n})\n// Promise { '\u003cresult\u003e' }\n\np.async.fromErr(p.async.fromVal('\u003cresult\u003e')).mapErr(val =\u003e {\n  console.log(val)\n})\n// \u003cresult\u003e\n```\n\n### Unicast vs Broadcast\n\nThe [GTOR](https://github.com/kriskowal/gtor) defines a \"task\" as a unit of delayed work that has only one consumer. GTOR calls this _unicast_ as opposed to promises which have multiple consumers and are therefore _broadcast_.\n\nWhy are promises broadcast, and Posterus unicast? My reasons are somewhat vague.\n\nAsync primitives should be modeled after synchronous analogs:\n\n* Sync → async: it guides the design; the user knows what to expect.\n\n* Async → sync: we can use constructs such as coroutines that convert async primitives back to the sync operations that inspired them.\n\nLet's see how promises map to synchronous constructs:\n\n```js\nconst first  = '\u003csome value\u003e'\nconst second = first  // share once\nconst third  = first  // share again\n\nconst first  = Promise.resolve('\u003csome value\u003e')\nconst second = first.then(value =\u003e value)  // share once\nconst third  = first.then(value =\u003e value)  // share again\n```\n\nJS promises are modeled after constants. They correctly mirror the memory model of a GC language: each value can be accessed multiple times and referenced from multiple places. You could call this _shared ownership_. For this reason, they _have_ to be broadcast.\n\nIncidentally, research into automatic resource management has led C++ and Rust people away from shared ownership, towards _exclusive ownerhip_ and [_move semantics_](https://doc.rust-lang.org/book/second-edition/ch04-01-what-is-ownership.html). Let's recreate the first example in Rust:\n\n```rust\n// This compiles if `Resource` derives the `Copy` trait, but types with\n// destructors don't have that option.\nfn main() {\n  #[derive(Debug)]\n  struct Resource{}\n\n  let first  = Resource{};\n  let second = first;\n  let third  = first;      // compile error: use after move\n  println!(\"{:?}\", first); // compile error: use after move\n}\n```\n\nWith that in mind, look at Posterus:\n\n```js\nimport * as p from 'posterus'\n\nconst task  = p.async.fromVal('\u003csome value\u003e')\ntask.mapVal(value =\u003e value) // returns same instance\ntask.mapVal(value =\u003e value) // returns same instance\n```\n\nIn Posterus, mapping mutates the task, making it impossible to preserve and access any of the previous steps. It's dramatically simpler and more efficient, but more importantly, it makes it possible to define sensible cancelation semantics.\n\nPosterus is unicast because it mirrors the memory model not of JavaScript, but of _Rust_. In Rust, taking a value _moves_ it out of the original container. (It also has borrowing, which is impractical for us to emulate.) I believe Rust's ownership model is a prerequisite for automatic resource management, the next evolution of GC.\n\nWhy force this into a GC language? Same reason C++ and Rust folks ended up with exclusive ownership and move semantics: it's a better way of dealing with non-trivial resources such as files, network sockets, and so on. Exclusive ownerhip makes it easy to deterministically destroy resources, while shared ownerhip makes it exceedingly difficult.\n\nThis idea of exclusive ownership lets you implement automatic resource management. Implicit, deterministic destructors in JavaScript? Never leaking websockets or subscriptions? Yes please! See [Espo → Agent](https://mitranim.com/espo/#agent-value-).\n\n### Why not Rx observables?\n\nThis is not specifically about Posterus, but seems to be a common sentiment.\n\nSince Rx observables appear to be a superset of promises and streams, some people suggest using them for everything. I find this view baffling. It implies the desire for more layers of crap, more API surface, more freedom for things to go wrong, and I don't know what to tell these people.\n\nOne reason would be that observables are not a good building block for coroutines. Coroutines map asynchronous primitives to synchronous constructs. Promises map to constants, streams map to iterators. Since an Rx observable is a superset of both, you can't map it to either without downcasting it to a promise or a stream, proving the need for these simpler primitives.\n\nAnother reason is API surface and learning curve. We need simple primitives for simple tasks, going to bigger primitives for specialized tasks. Promises are hard enough. Don't burden a novice with mountainous amounts of crap when promises satisfy the use case.\n\nSince we're talking observables, here's a bonus: a [different breed](https://mitranim.com/espo/#atom-value-) of observables especially fit for GUI apps. It enables [implicit GUI reactivity](https://mitranim.com/espo/#react-views) and automatic resource management with deterministic destructors (see above).\n\n### Why not Bluebird?\n\nBluebird now supports upstream cancelation. Why not use it and tolerate the other [promise annoyances](#3-annoyances-in-the-standard)?\n\n* The size kills it. At the moment of writing, the core Bluebird bundle is 56 KB minified. For a browser bundle, that's insanely large just for cancelation support. Not caring about another 50 KB is how you end up with megabyte-large bundles that take seconds to execute. Posterus comes at 6 KiB, like a typical promise polyfill.\n\n* At the moment of writing, Bluebird doesn't cancel promises that lose a `Promise.race`. I disagree with these semantics. Some use cases demand that losers be canceled. See the [timeout race example](#1-race-against-timeout).\n\n## Usage\n\nExample of wrapping `XMLHttpRequest` with a cancelable task:\n\n```js\nimport * as p from 'posterus'\n\nconst task = new p.Task()\nconst xhr = new XMLHttpRequest()\n\n// Oversimplified. Use the `xhttp` library instead.\nfunction onXhrDone({target: xhr, type: reason}) {\n  const {status, responseText: body} = xhr\n\n  if (!(status \u003e= 200 \u0026\u0026 status \u003c 300)) {\n    task.done(Error(body))\n    return\n  }\n\n  const response = {xhr, status, head: xhr.getAllResponseHeaders(), body}\n  task.done(undefined, response)\n}\n\n// Automatically called when the task is deinited, either directly via\n// `task.deinit()`, or indirectly from another task.\ntask.onDeinit(xhr.abort.bind(xhr))\n\n// Similar to `promise.then`. Chainable. See the API below.\ntask.map((err, val) =\u003e {\n  console.log(err, val)\n})\n\nxhr.onerror = xhr.onload = xhr.ontimeout = onXhrDone\nxhr.open('get', '/')\nxhr.send()\n\n// Aborts the request.\ntask.deinit()\n```\n\n## API\n\nAll examples imply an import:\n\n```js\nimport * as p from 'posterus'\n```\n\n### `Task()`\n\nCreates a pending task that can be settled with [`.done()`](#taskdoneerr-val) or canceled with [`.deinit()`](#taskdeinit).\n\n```js\nconst task = new p.Task()\n\ntask\n  .map((err, val) =\u003e {\n    console.log(err, val)\n  })\n  .mapVal(val =\u003e {\n    console.log(val)\n  })\n  .mapErr(err =\u003e {\n    console.warn(err)\n  })\n\n// Eventually, trigger the chain:\ntask.done(undefined, '\u003cresult\u003e')\n\n// Or cancel:\ntask.deinit()\n```\n\n#### `task.isDone()`\n\nReturns `true` if the task has been settled or deinited:\n\n```js\nconst task = new p.Task()\ntask.isDone() // false\ntask.done()\ntask.isDone() // true\n```\n\nNote that unlike `Promise`, Posterus' `Task` does _not_ store its result. This allows to dramatically simplify the API and implementation.\n\n#### `task.done(err, val)`\n\nSettles the task with the provided error and result. Similar to the `resolve` and `reject` callbacks in a `Promise` constructor, but as an instance method, combined into one \"errback\" signature. Can be called at any point after creating the task.\n\nThe task is considered rejected if `error` is truthy, and successful otherwise, like in a typical Node errback.\n\nUnlike promises, a task runs its callbacks _synchronously_. If there's an unhandled error, the caller of `.done()` can/must handle it via try/catch. This dramatically simplifies the implementantion, the mental model, and helps to avoid unhandled rejections.\n\nEither `err` or `val` can be a task. In this case, it's \"flattened\": the current task will wait for its completion. In addition, the current task takes \"ownership\" of any task passed to `.done()`, and will deinit it alongside itself on a call to [`.deinit()`](#taskdeinit).\n\nIf the task has previosly been settled or deinited, this is a no-op.\n\n```js\n// Synchronous exception.\nnew p.Task().done(Error('\u003cerror\u003e'))\n\nconst task = new p.Task()\ntask.done(undefined, '\u003cresult\u003e')\ntask.mapVal(val =\u003e {\n  console.log(val)  // '\u003cresult\u003e'\n})\n\n// Flattens provided task.\nconst task = new p.Task()\ntask.done(undefined, p.async.fromVal('\u003ctask result\u003e'))\ntask.mapVal(val =\u003e {\n  console.log(val)  // '\u003ctask result\u003e'\n})\n\n// Waits for provided task.\nconst task = new p.Task()\nconst inner = new p.Task()\ntask.done(undefined, inner)\ntask.mapVal(val =\u003e {\n  console.log(val)  // '\u003casync result\u003e'\n})\ninner.done(undefined, '\u003casync result\u003e')\n```\n\n#### `task.map(fun)`\n\nwhere `fun: ƒ(err, val): any`\n\nCore chaining operation. Registers a function that will transform the task's result or error. The function's return value becomes the task's result, and the function's throw becomes the task's error. Either may be further transformed by other mappers.\n\nCompared to promises, this is like a combination of `.then()` and `.catch()` into one function. Unlike promises, this _mutates the task and returns the same instance_.\n\nBecause Posterus tasks don't store their results, calling `.map()` after the task is settled (via `.done()`) produces a synchronous exception. For asynchrony, use [`async`](#async) and [`AsyncTask`](#asynctask).\n\nJust like [`.done()`](#taskdoneerr-val), this automatically \"flattens\" the tasks returned or thrown by the mapper(s), eventually resolving to non-task values. This is known as \"flatmap\" in some languages.\n\nTakes \"ownership\" of any task returned or thrown by a mapper, and will deinit the inner task on a call to [`.deinit()`](#taskdeinit).\n\nAll other chaining operations are defined in terms of `.map()` and share these characteristics.\n\n```js\np.async.fromVal('\u003cmessage\u003e')\n  // This could blow up the chain!\n  .map((_err, val) =\u003e {\n    throw Error(val)\n  })\n  // This \"catches\" the error and converts it back into a result.\n  .map((err, val) =\u003e {\n    // The chain will automatically \"flatten\", waiting for this task.\n    return p.async.fromVal(err.message)\n  })\n  // Guaranteed no error.\n  .map((_err, val) =\u003e {\n    console.log(val)  // '\u003cmessage\u003e'\n  })\n```\n\n#### `task.mapErr(fun)`\n\nwhere `fun: ƒ(err): any`\n\nVariant of [`.map()`](#taskmapfun) where the function is called only for errors, like `.catch()` in promises.\n\n```js\np.async.fromErr(Error('\u003cfail\u003e'))\n  // Converts error to value.\n  .mapErr(err =\u003e err.message)\n  // Called with value, not error.\n  .map((_err, val) =\u003e {\n    console.log(val)  // '\u003cfail\u003e'\n  })\n\np.async.fromVal('\u003cok\u003e')\n  // Won't be called because no error.\n  .mapErr(err =\u003e {\n    console.error('Oh noes! Panic!')\n    process.exit(1)\n  })\n  .map((_error, val) =\u003e {\n    console.log(val)  // '\u003cok\u003e'\n  })\n```\n\n#### `task.mapVal(fun)`\n\nwhere `fun: ƒ(val): any`\n\nVariant of [`.map()`](#taskmapfun) where the function is called only for non-errors, like `.then()` in promises.\n\n```js\np.async.fromErr(Error('\u003cfail\u003e'))\n  // Won't be called because there's an error.\n  .mapVal(val =\u003e {\n    console.log(val)\n    console.log('Got it! I quit!')\n    process.exit(0)\n  })\n  .map((err, _val) =\u003e {\n    console.warn(err)  // Error('\u003cfail\u003e')\n  })\n\np.async.fromVal('\u003cok\u003e')\n  // Transforms the result.\n  .mapVal(val =\u003e {\n    return [val]\n  })\n  .map((_error, val) =\u003e {\n    console.log(val)  // ['\u003cok\u003e']\n  })\n```\n\n#### `task.finally(fun)`\n\nwhere `fun: ƒ(err, val): void`\n\nVariant of [`.map()`](#taskmapfun) that doesn't change the result.\n\nLike in synchronous `try/finally`, if this function throws, the resulting error overrides the previous result or error. Unlike `try/finally`, a value returned by this function does not override the previous result.\n\nBecause the return value of the function is ignored, this _does not_ flatmap any returned tasks. Use `.finally` only for synchronous operations.\n\n```js\np.async.fromVal('\u003cresult\u003e')\n  // Does not change the result.\n  .finally((err, val) =\u003e {\n    console.log(err, val) // undefined, \u003cresult\u003e\n    return '\u003cignored\u003e'\n  })\n  .mapVal(val =\u003e {\n    console.log(val) // '\u003cresult\u003e'\n  })\n\np.async.fromErr(Error('\u003cfail\u003e'))\n  // Does not \"catch\" the error.\n  .finally(err =\u003e {\n    console.log(err) // Error('\u003cfail\u003e')\n  })\n  .mapErr(err =\u003e {\n    console.warn(err) // Error('\u003cfail\u003e')\n  })\n```\n\nNote that since version `0.5.0`, you must use [`task.onDeinit`](#taskondeinitfun) rather than `.finally` to register cleanup functions.\n\n#### `task.onDeinit(fun)`\n\nwhere `fun: ƒ(): void`\n\nRegisters a function that will be called when the task is deinited, either directly or through its descendants. Can be called multiple times to register multiple functions. Use it for cleanup:\n\n```js\nconst task = new p.Task()\n\n// Represents an arbitrary async operation.\n// Could be an HTTP request, etc.\nconst timer = setTimeout(() =\u003e {task.done()})\n\nconst cancel = clearTimeout.bind(undefined, timer)\n\ntask.onDeinit(cancel)\n\ntask\n  // Never called because of subsequent deinit.\n  .mapVal(() =\u003e {throw Error('panic')})\n  .deinit()\n```\n\n#### `task.deinit()`\n\nSynchronously aborts the task. Prevents any [`.map`](#taskmapfun)-based callbacks from being invoked. If the task was waiting on an inner task, calls `.deinit` on the inner task. Synchronously calls any functions registered by [`.onDeinit`](#taskondeinitfun).\n\n```js\nconst task = new p.Task()\n\n// Represents an arbitrary async operation.\n// Could be an HTTP request, etc.\nconst timer = setTimeout(() =\u003e {task.done()})\n\nconst cancel = clearTimeout.bind(undefined, timer)\n\ntask.onDeinit(cancel)\n\ntask\n  // Never called because of subsequent deinit.\n  .mapVal(() =\u003e {throw Error('panic')})\n  .deinit()\n```\n\n#### `task.toPromise()`\n\nConverts the task to a promise, using the standard `Promise` constructor, which must exist in the global environment. Mutates the task by calling [`.map`](#taskmapfun), transforming its result to `undefined`.\n\nDeiniting the original task causes the resulting promise to be rejected with an error.\n\n```js\nconst task = p.async.fromVal('\u003cresult\u003e')\n\ntask\n  .toPromise()\n  // Would log '\u003cresult\u003e' but never gets called, see below.\n  .then(val =\u003e {\n    console.log(val)\n  })\n  .catch(err =\u003e {\n    console.warn(err) // Error('deinit')\n  })\n\npromise instanceof Promise // true\n\ntask.deinit() // Rejects the promise.\n```\n\n### `Scheduler()`\n\nUtility for settling tasks asynchronously. One global instance is exposed as\n[`async`](#async).\n\n#### `scheduler.push(task, err, val)`\n\nWill call `task.done(err, val)` after a small delay. Used internally by `.fromErr` and `.fromVal`.\n\n#### `scheduler.fromErr(err)`\n\nSimilar to `Promise.reject`. Returns a new task for which `task.done(err, undefined)` will be called after a small delay. Usually invoked on the global `async` instance:\n\n```js\nconst task = p.async.fromErr(Error('fail'))\n\ntask.mapErr(err =\u003e {\n  console.warn(err)\n})\n```\n\n#### `scheduler.fromVal(val)`\n\nSimilar to `Promise.resolve`. Returns a new task for which `task.done(undefined, val)` will be called after a small delay. Usually invoked on the global `async` instance:\n\n```js\nconst task = p.async.fromVal('\u003cresult\u003e')\n\ntask.mapVal(val =\u003e {\n  console.log(val)\n})\n```\n\n#### `scheduler.tick()`\n\nAttempts to finish all pending async operations, _right now_. Gives you more\ncontrol over _time_, allowing to \"opt out\" of asynchrony in situations that\ndemand synchronous execution.\n\nThe scheduler flushes all pending tasks by calling [`task.done`](#taskdoneerr-val) on each. Then, `task.done` synchronously calls functions registered via [`task.map`](#taskmapfun) and derivatives. As a result, this will run all task-related callbacks that could possibly run now.\n\nUsually invoked on the global [`async`](#async) instance. Example:\n\n```js\n// Delayed, doesn't run yet.\np.async.fromVal('\u003cresult\u003e')\n  .mapVal(val =\u003e {\n    console.log(val)\n  })\n\n// Immediately runs the callback above, logging '\u003cresult\u003e'.\np.async.tick()\n```\n\n### `AsyncTask()`\n\nVariant of [`Task`](#task) whose [`.done()`](#taskdoneerr-val) is asynchronous. Instead of settling the task and calling mapper functions immediately, calling `.done()` schedules the task to be settled after a small delay.\n\nUses the default global scheduler [`async`](#async). Can be immediately flushed via [`async.tick()`](#schedulertick):\n\n```js\nconst task = new p.AsyncTask()\n\n// Schedules to be settled after a small delay.\ntask.done(undefined, '\u003cresult\u003e')\n\n// Unlike `Task`, calling `.map` after `.done` is allowed.\ntask.mapVal(val =\u003e {\n  console.log(val)\n})\n\n// Immediately runs the callback above, logging '\u003cresult\u003e'.\np.async.tick()\n```\n\n### `async`\n\nGlobal instance of [`Scheduler`](#scheduler). This is never implicitly used by `Task`. Asynchrony is always opt-in. See the [`Scheduler`](#scheduler) examples above.\n\n### `isTask(val)`\n\nDefines the \"task interface\". All Posterus functions and methods that accept tasks test their inputs via this function, allowing external implementations, and without any \"secret fast paths\" for Posterus' own classes.\n\n```js\np.isTask(new p.Task())  // true\np.isTask(p.Task)        // false\n```\n\n### `branch(task)`\n\nCreates a \"weakly held\" branch that doesn't \"own\" the parent task. It inherits the original's result and cancelation, but does not change the original's result, and deiniting a branch does not deinit the original.\n\nMind the order: because [`.map`](#taskmapfun) mutates a task, you need to register branches _before_ further mapping the original. For example, to handle the original's error, you must create branches first, _then_ use [`.mapErr`](#taskmaperrfun).\n\n```js\nconst trunk = p.async.fromErr(Error('\u003cerror\u003e'))\nconst branch0 = p.branch(trunk).mapErr(console.warn)\nconst branch1 = p.branch(trunk).mapErr(console.warn)\n\n// Handle the original's error. Must be called after branching.\ntrunk.mapErr(console.warn)\n\n// Has no effect on the trunk or other branches.\nbranch0.deinit()\nbranch1.deinit()\n\n// This WILL deinit the branches.\ntrunk.deinit()\n```\n\n### `all(list)`\n\nSimilar to `Promise.all`. Takes a list of values, which may or may not be tasks, and returns a single task that waits for them to complete. The resulting task is eventually settled with a list of results.\n\nUnlike `Promise.all`, supports cancelation:\n\n* On [`.deinit()`](#taskdeinit), deinits all underlying tasks.\n\n* On error, deinits all underlying tasks that are still pending.\n\n```js\np.all([\n  'one',\n  p.async.fromVal('two'),\n  p.async.fromVal().mapVal(() =\u003e 'three'),\n])\n.mapVal(vals =\u003e {\n  console.log(vals) // ['one', 'two', 'three']\n})\n\np.all([\n  p.async.fromErr(Error('err')),\n  // Will NOT be called: the error above causes deinit.\n  p.async.fromVal().mapVal(panic),\n])\n.mapErr(err =\u003e {\n  console.warn(err) // Error('err')\n})\n\n// If this ever runs, it might crash the process.\n// `all` will make sure it doesn't happen.\nfunction panic() {\n  console.error(Error('panic'))\n  process.exit(1)\n}\n```\n\n### `dictAll(dict)`\n\nSame as [`all`](#alllist), but the input and output are dicts. Has the same cancelation semantics.\n\n```js\np.all({\n  one: 10,\n  two: p.async.fromVal(20),\n})\n.mapVal(vals =\u003e {\n  console.log(vals) // {one: 10, two: 20}\n})\n```\n\n### `race(list)`\n\nSimilar to `Promise.race`. Takes a list of values, which may or may not be tasks, and returns a single task that resolves with the _first_ error or value that \"wins\" the race.\n\nUnlike `Promise.race`, this automatically deinits every task that didn't \"win\".\n\n```js\np.race([\n  // Wins the race.\n  p.async.fromVal('\u003cresult\u003e'),\n  // Loses the race and gets deinited.\n  p.async.fromVal().mapVal(panic),\n]).mapVal(val =\u003e {\n  console.log(val) // '\u003cresult\u003e'\n})\n\np.race([\n  p.async.fromVal().mapVal(panic),\n  p.async.fromVal().mapVal(panic),\n])\n// Cancels all competitors.\n.deinit()\n\n// Still alive!\n\n// If this ever runs, it might crash the process.\n// `race` will make sure it doesn't happen.\nfunction panic() {\n  console.error(Error('panic'))\n  process.exit(1)\n}\n```\n\n### `fromPromise(promise)`\n\nInterop utility. Converts a promise to a task. Also see [`toTask`](#totaskval).\n\n```js\nconst promise = Promise.resolve('\u003cvalue\u003e')\nconst task = p.fromPromise(promise)\n```\n\n### `toTask(val)`\n\nInterop utility. Converts any value to a task. Tasks are returned as-is; promises are converted via `fromPromise`; other values are scheduled on the default scheduler instance via [`scheduler.fromVal(val)`](#schedulerfromvalval).\n\n```js\nconst task0 = p.toTask(p.async.fromVal(10)) // Returned as-is.\nconst task1 = p.toTask(Promise.resolve(20))\nconst task2 = p.toTask(30)\n```\n\n## API (`fiber.mjs`)\n\nThe optional module `posterus/fiber.mjs` implements fibers (coroutines) via generators. Superior replacement for `async` / `await`, with implicit cancelation of in-progress work.\n\nBasic usage:\n\n```js\nimport * as p from 'posterus'\nimport * as pf from 'posterus/fiber.mjs'\n\nfunction* simpleGen(val) {\n  val = yield val\n  return val\n}\n\nconst fiberSync = pf.fiber(simpleGen)\nconst fiberAsync = pf.fiberAsync(simpleGen)\n\nconst val = fiberSync('val')\nconst task = fiberAsync('val')\n```\n\nFibers are composable: they can wait on tasks or iterators returned by generator functions. They support automatic cleanup: deiniting an outer fiber that's blocked on an inner fiber will deinit both.\n\n```js\nimport * as p from 'posterus'\nimport * as pf from 'posterus/fiber.mjs'\n\nconst outer = pf.fiber(function* (val) {\n  val = yield inner(val)\n  return val + 10\n})\n\nconst inner = pf.fiber(function* (val) {\n  val = yield p.async.fromVal(val)\n  return val + 10\n})\n\n// Currently waiting on `inner` and `fromVal`.\nconst task = outer(10)\n\ntask.mapVal(console.log) // Would log \"30\" unless deinited.\n\n// Deinits all three tasks: outer, inner, and `fromVal`.\ntask.deinit()\n```\n\n### `Fiber()`\n\nSubclass of [`Task`](#task) that tracks the lifecycle of an iterator object returned by a generator function. Created by all functions in this module. You shouldn't need to construct it directly, but it's exported for completeness, as a building block.\n\nA newly-constructed `Fiber` is inert; it doesn't immediately \"enter\" the procedure, and is pending forever. To start it, call `.done()`.\n\n```js\nfunction* gen() {}\n\nconst fib = new pf.Fiber(gen())\n\nfib.map((err, val) =\u003e {/* ... */})\n\n// Starts execution. May synchronously return the result, if possible.\nconst val = fib.done()\n```\n\n### `fiber(fun)`\n\nWraps a generator function. The resulting function invokes [`fromIter`](#fromiteriter), returning either the resulting value (if possible), or a pending task.\n\n```js\nconst fibSync = pf.fiber(function* genSync(val) {\n  val = (yield val) + 10\n  return val\n})\n\nconst fibAsync = pf.fiber(function* genAsync(val) {\n  val = (yield p.async.fromVal(val)) + 10\n  return val\n})\n\nconsole.log(fibSync(10))         // 20\nconsole.log(fibAsync(10))        // Task {}\nfibAsync(10).mapVal(console.log) // 20\n```\n\n### `fiberAsync(fun)`\n\nWraps a generator function. The resulting function invokes [`fromIterAsync`](#fromiterasynciter), always returning a pending task. Note that `fromIterAsync` does _not_ immediately start execution; the wrapped function is always scheduled to execute asynchronously, but can be flushed synchronously via [`async.tick()`](#schedulertick).\n\n```js\nconst fibAsync0 = pf.fiberAsync(function* genSync(val) {\n  val = (yield val) + 10\n  return val\n})\n\nconst fibAsync1 = pf.fiberAsync(function* genAsync(val) {\n  val = (yield p.async.fromVal(val)) + 10\n  return val\n})\n\nconsole.log(fibAsync0(10))        // Task {}\nconsole.log(fibAsync1(10))        // Task {}\nfibAsync0(10).mapVal(console.log) // 20\nfibAsync1(10).mapVal(console.log) // 20\n```\n\n### `fromIter(iter)`\n\nTakes an iterator object (returned by calling a generator function) and attempts to execute it synchronously. If successful, returns the resulting value or throws an error. Otherwise, returns a pending task.\n\n```js\nfunction* genSync(val) {\n  val = (yield val) + 10\n  return val\n}\n\nfunction* genAsync(val) {\n  val = (yield p.async.fromVal(val)) + 10\n  return val\n}\n\nconst val  = pf.fromIter(genSync(10))  // 20\nconst task = pf.fromIter(genAsync(10)) // Task {}\ntask.mapVal(console.log)               // 20\n```\n\n### `fromIterAsync(iter)`\n\nTakes an iterator object (returned by calling a generator function) and schedules it to be executed asynchronously, on the default scheduler [instance](#async). Returns a pending task. Can be flushed synchronously via [`async.tick()`](#schedulertick).\n\n```js\nfunction* genSync(val) {\n  val = (yield val) + 10\n  return val\n}\n\nfunction* genAsync(val) {\n  val = (yield p.async.fromVal(val)) + 10\n  return val\n}\n\nconst task0 = pf.fromIterAsync(genSync(10))  // Task {}\nconst task1 = pf.fromIterAsync(genAsync(10)) // Task {}\ntask0.mapVal(console.log)                    // 20\ntask1.mapVal(console.log)                    // 20\n```\n\n## Changelog\n\n### 0.6.1\n\nFixed an edge case where fibers would swallow exceptions thrown by mappers.\n\nSlightly reduced the unminified size of both files.\n\n### 0.6.0\n\n**Revised fibers**: dramatically simpler, more efficient, fully synchronous by default. Async is opt-in. Breaking API changes:\n\n  * `pf.fiber` now wraps a generator function, returning a function that uses `pf.fromIter`.\n\n  * Added `pf.fiberAsync`: wraps a generator function, returning a function that uses `pf.fromIterAsync`.\n\n  * Added `pf.fromIter`: takes an iterator and immediately executes, returning a non-task value if possible, otherwise returning a pending task.\n\n  * Added `pf.fromIterAsync`: takes an iterator and schedules its execution on the default `p.Scheduler` (`p.async`), returning a pending task.\n\n**Breaking**: removed automatic from-promise conversion. When using promise-returning functions, follow up with `p.fromPromise`:\n\n```js\nconst task = new p.Task()\n\ntask\n  .mapVal(() =\u003e Promise.resolve('val'))\n  .mapVal(p.fromPromise)\n\ntask.done(undefined, p.fromPromise(Promise.resolve('val')))\n```\n\nRelatively **non-breaking**: `task.done()` now returns either the resulting value (if finished just now), or the task itself (if pending). The value is not stored; subsequent `task.done()` on a completed task returns `undefined`. This is useful in edge cases; for example, it allows the fiber implementation to be significantly simpler and more efficient.\n\n**Non-breaking**: added `toTask` for converting arbitrary values to tasks.\n\n### 0.5.1\n\n`task.done()` and `task.map()`, and other methods defined in terms of them, now implicitly support promises, waiting for their completion.\n\nRestored the ability to wait on tasks and promises passed as errors to `.done()` or thrown by mappers, which was erroneously omitted in the rework.\n\n### 0.5.0\n\nBreaking revision:\n\n  * Dramatically simpler and faster.\n  * Mostly synchronous.\n  * Mapping mutates the instance instead of allocating a new one.\n  * Deinit callbacks are registered separately, instead of using `.finally`.\n  * Cancelation is silent, without special errors.\n  * Renamed \"future\" to \"task\".\n  * Provides only native JS modules.\n\nThe new design had incubated in production for about 2 years before being published, and is considered mature.\n\nNow licensed under [Unlicense](https://unlicense.org).\n\n### 0.4.6\n\nWith Webpack or other bundlers, `import 'posterus/fiber'` now chooses the ES2015 version, whereas `require('posterus/fiber')` in Node still chooses the CommonJS version.\n\n### 0.4.4, 0.4.5\n\nAdded ES modules. When using `import 'posterus'`, bundlers such as Webpack should automatically use the version from the `es` folder. This makes it compatible with module concatenation, tree shaking, etc.\n\n### 0.4.3\n\nFuture.all no longer gets slower with large input arrays.\n\nProbably irrelevant in real world code, but could make us look bad in artificial microbenchmarks.\n\n### 0.4.2\n\nBugfixed a rare edge case in `.all` and `.race`.\n\n### 0.4.1\n\nUnignored `posterus/fiber` for NPM and removed `posterus/routine`.\n\n### 0.4.0: dependency-free, smaller size\n\nInlined the `fastqueue` dependency with modifications that reduce the minified size. Use ES5-style classes and other tweaks to avoid generating Babel garbage and produce minificable code. This reduces the total size from ≈8 KiB to ≈6 KiB.\n\n### 0.3.4: better fiber promise support\n\nYou can now also `return` promises from fibers.\n\n### 0.3.3: fiber promise support\n\nYou can now `yield` promises in fibers.\n\n### 0.3.2: bugfix\n\nCorrected an obscure fiber bug.\n\n### 0.3.1: renamed `routine` → `fiber`\n\nÆsthetic change. Renamed coroutines to fibers:\n\n```js\nconst {fiber} = require('posterus/fiber')\nconst future = fiber(function*() {}())\n```\n\nThe `require('posterus/routine').routine` export worked until `0.4.1`.\n\n### 0.3.0: breaking changes focused on termination\n\nBig conceptual revision. Now, \"cancelation\" is defined as settling the future chain with an error. Posterus no longer cancels your callbacks, so your code always runs and terminates.\n\nThis also removes `Future.init`, `Future.initAsync`, and special cancelation-only callbacks they supported. They become unnecessary when cancelation is just an error: deinit logic can be put in a `.finally` callback. When deiniting a descendant future, callbacks on ancestor futures are called synchronously to ensure immediate cleanup.\n\nThe new behavior better aligns with synchronous code. Consider Java threads and futures: `thread.stop()`, `thread.interrupt()`, `future.cancel()` throw an exception into a thread, moving execution to the nearest `catch` or `finally` block. It doesn't completely stop the thread's code from executing, and there aren't any special cancelation-only blocks of code.\n\nOther changes and improvements:\n\n  * `.all` and `.race` created from deinited futures will now settle with an\n    error instead of hanging up\n\n  * empty `.race([])` now immediately settles with `undefined` instead of\n    hanging up\n\n  * deiniting a future after settling no longer prevents callbacks from running;\n    instead, this suppresses the rejection handler and deinits its ancestors, if\n    any\n\n  * weaks created before and after settling a future settle in the same order\n\n  * better compatibility with subclassing\n\n## License\n\nhttps://unlicense.org\n\n## Misc\n\nI'm receptive to suggestions. If this library _almost_ satisfies you but needs changes, open an issue or chat me up. Contacts: https://mitranim.com/#contacts\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitranim%2Fposterus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmitranim%2Fposterus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitranim%2Fposterus/lists"}