async-endpoint
v0.0.23
Published
![Async Endpoint Logo](logo.png)
Downloads
199
Readme
Async Endpoint
Asynchronous Endpoints, especially when paired with a functional style of programming, provide a sound method of writing programs in JavaScripts that are
- testable
- maintainable
- extendable
- composeable
- easy to reason about
- standard based*
This repository provides a brief intoduction to asynchronous endpoints**, along with a helper library async-endpoint
to help make a few things easier.
Table of contents
Introduction
- Synchronous Endpoints
- Synchronous Iteration
- Asynchronous Iteration
- Asynchronous Input
- Import
- API
Introduction
Many programming languages use the concept of functions as entry points to transfer control between programs.
Synchronous Endpoints
In javascript, this is as simple as writing a function and calling it:
Example 1
const program = function() {
console.log("hello world");
};
program();
//logs "hello world"
Taking a queue from functional programming, we can remove the logging side affect from the main program into a separate render function that logs the result retuned from running:
Example 2
const render = console.log;
const program = function() {
return "hello world";
};
render(program());
//logs "hello world"
Synchronous Iteration
Provided that our render function expects an iterator, We can construct our program with a generator function to yeild multiple results:
Example 3
const render = iterator => {
//as the result is an iterator
//we iterate though it and log each subsequent result
for (const result of iterator) {
console.log(result);
}
};
const program = function*() {
yield "hello";
yield "world";
};
render(program());
//logs "hello"
//logs "world"
Asynchronous Iteration
When using asynchronous generators, we can await asynchronous APIs, though we must again make sure to modify our fetch function.
Example 4
const render = async asynchronousIterator => {
//we use the "for await" construct for Asynchronous Iterators
for await (const result of asynchronousIterator) {
console.log(result);
}
};
const program = async function*() {
yield "fetching...";
yield await fetch("https://www.google.com");
yield "results fetched.";
};
render(program());
//logs "fetching..."
//logs result from fetch.
//logs "results fetched."
Asynchronous Input and Interactive Programs
With a few small tricks, asynchronous generators can function as fully interactive programs.
We'll take advantage of the included AsyncArray class to derive a "request" and a "respond" function.
When a program calls request, it will return a promise. This promise will then be fulfilled with the input of the next call to respond.
import { AsyncArray } from "async-endpoint";
const channel = new AsyncArray();
const respond = channel.push.bind(channel),
request = async ()=>(await channel.next()).value;
By convention, we'll pass two arguments to our asynchronous generator function:
- an _init_ object, which may or may not be ignored
- the aforementioned _request_ function
const program = async function *(init, request){
...
}
Finally, we need to connect the respond object to user input. If running in a browser, you could simply attach it to the window object:
window.respond = respond;
Or, if running in node, you can use the included inputConsole
function:
import { inputConsole } from "async-endpoint";
inputConsole(respond);
Puttig it all together, we can write an interactive program like this:
(Note that instead of defining a render function, we're using the
generic renderer
from the async-endpoit
library instead of writing our own this time).
Example 5
import { AsyncArray, inputConsole, renderer } from "async-endpoint";
const render = renderer();
const program = async function*(init, request) {
yield "What's your name?";
yield `Hello ${await request()}`;
};
const channel = new AsyncArray();
const respond = channel.push.bind(channel),
request = async ()=>(await channel.next()).value;
inputConsole(respond);
render(program(undefined, request)); //the init object will be ignored
//logs "
Related
Import
There are a few ways to import this into your project.
Pre-Ecmascript Modules
Common JS
The package's main file is a compiled common JS file.
const AsyncEndpoint = require("async-endpoint");
const AsyncEndpoint = require("async-endpoint/common.js"); //also works
This should work with file bundlers, though I haven't had a chance to test it.
import * as AsyncEndpoint from = "async-endpoint/common.js";
Browser
There is also a rollup of the package for the browser, though there are a number of issues getting this to work out of the box. You're probably better off pointing directly to the files in the "js" or "mjs" folders and using a bundler.
<script src = ".../async-endpoint/browser.js"></script>
<script>
alert(typeof window.AsyncEndpoint);
</script>
Ecmascript Modules
Ecmascript modules are available in two flavors of ecmascript modules:
MJS
In a node application, the "import" keyword can be used to import the package.
//index.mjs
import * as AsyncEndpoint from "async-endpoint/mjs";
As of this writing, node requires the the "experimental-modules" and "harmony_async_iteration" flags to be set, but this will change once a the "import" and "asynchronous iterator" features hae landed.
node --experimental-modules --harmony_async_iteration index.mjs
If you wish to avoid experimental the features, use the above common.js module.
JS
In a browser application, the "import" keyword can be used to import the package.
When using ".mjs" files, a server may fail to serve the proper "application/javascript" mime type causing the application to fail. As such, the "js" folder is included.
<script>
import * as AsyncEndpoint from "async-endpoint/js/index.js";
<script>
alert(typeof window.AsyncEndpoint);
</script>
</script>
As of this writing, only Chrome supports the necessary import and asynchronous interation features necessary to get this to work.
To ensure compatibility with other browsers use the above browser.js module. You can also re-bundle either the flow, js, or mjs folders.
//Use
import map from "async-endpoint/js/array-like/map.js";
//rather than
import {map} from "async-endpoint/js/index.js";
API
Classes
Functions
Typedefs
AsyncArray ⇐ Array
Kind: global class
Extends: Array
new AsyncArray()
An Asynchronous Array
Example
import {AsyncArray} from "async-endpoint";
const input = new AsyncArray();
const main = async()=>{
setTimeout(()=>{
input.push("hello world");
})
const {value} = await input.next();
console.log(value);
}
main();
//logs "hello world"
Example
import {AsyncArray} from "async-endpoint";
const input = new AsyncArray();
const main = async()=>{
setTimeout(()=>{
input.push("hello");
input.push("world");
})
for await(const value of input){
console.log(vaue);
}
}
main();
//logs "hello"
//logs "world"
composePrograms(request, ...programs) ⇒ AsynchornousIterator
composes programs sequentially with a single input
Kind: global function
Returns: AsynchornousIterator - resulting iterator
| Param | Type | Description | | --- | --- | --- | | request | function | request function for input | | ...programs | Program | programs to be composed sequentially |
Example
import {composePrograms, AsyncArray} from "async-endpoint";
import porgram1, program1, program3 from "....js";
const channel = new AsyncArray();
const respond = channel.push.bind(channel),
request = async () => (await channel.next()).value;
const program = composePrograms(request, program1, program2, program3);
window.respond = respond;
creates an iterator whose values are mapped from another(iterator, mapper) ⇒ AsynchornousIterator
Kind: global function
Returns: AsynchornousIterator - resulting iterator
| Param | Type | Description | | --- | --- | --- | | iterator | AsynchornousIterator | iterator to be mapped | | mapper | AsyncTransformer | transformation for individual items |
Example
import {map, continuousOutput}, from "async-endpoint";
let i = 0;
const mapped = map(continuousOutput(()=>i++), (n) => n + 2);
const main = async ()=>{
for await(item of mapped){
console.log(item);
}
}
main();
logs "2"
logs "3"
logs "4"
...
executes a provided funcition for each item of an iterator(iterator, handler) ⇒ undefined
Kind: global function
| Param | Type | Description | | --- | --- | --- | | iterator | AsynchornousIterator | iterator | | handler | AsyncTransformer | provided function |
Example
import {forEach, continuousOutput}, from "async-endpoint";
let i = 0;
forEach(continuousOutput(()=>i++, console.log));
main();
logs "2"
logs "3"
logs "4"
...
filter(iterator, filterer) ⇒ AsynchornousIterator
creates an iterator whose values are filtered from another
Kind: global function
Returns: AsynchornousIterator - filtered iterator
| Param | Type | Description | | --- | --- | --- | | iterator | AsynchornousIterator | iterator to be filtered | | filterer | function | boolean filtering function |
Example
import {filter, continuousOutput} from "async-endpoint";
let i = 0;
const filtered =filter(continuousOutput(()=>i++), (n)=>n%2);
const main = async ()=>{
for await(item of filtered){
console.log(item);
}
}
main();
logs "1"
logs "3"
logs "5"
reduce(iterator, reducer, [inital], [condition], [resetInitial]) ⇒ AsynchornousIterator
creates an iterator whose values are reduced from another
Kind: global function
Returns: AsynchornousIterator - reduced iterator
| Param | Type | Default | Description | | --- | --- | --- | --- | | iterator | AsynchornousIterator | | iterator to be reduced | | reducer | function | | reducing function | | [inital] | * | | initial object to reduce into | | [condition] | function | (item, initial) => false | boolean filtering function indicating when to start new reduction phase | | [resetInitial] | function | ()=>initial | method to reset/replace initial reduction object |
Example
import {reduce, continuousOutput} from "async-endpoint";
let i = 0;
const reduced = reduce(continuousOutput(()=>i++) , (previous, current)=>previous.push(current),[], (x)=!(x%5), ()=>([]));
const main = async ()=>{
for await(item of reduced){
console.log(item);
}
}
main();
logs "[0]"
logs "[1, 2, 3, 4, 5]"
...
reduceRight(iterator, reducer, [inital], [condition], [resetInitial]) ⇒ AsynchornousIterator
creates an iterator whose values are reduced from another
Kind: global function
Returns: AsynchornousIterator - reduced iterator
| Param | Type | Default | Description | | --- | --- | --- | --- | | iterator | AsynchornousIterator | | iterator to be reduced | | reducer | function | | reducing function | | [inital] | * | | initial object to reduce into | | [condition] | function | (item, initial) => false | boolean filtering function indicating when to start new reduction phase | | [resetInitial] | function | ()=>initial | method to reset/replace initial reduction object |
Example
import {reduce, continuousOutput} from "async-endpoint";
let i = 0;
const reduced = reduce(continuousOutput(()=>i++) , (previous, current)=>previous.push(current),[], (x)=!(x%5), ()=>([]));
const main = async ()=>{
for await(item of reduced){
console.log(item);
}
}
main();
logs "[0]"
logs "[1, 2, 3, 4, 5]"
...
pause(milliseconds, value) ⇒ Promise
returns a resolved promise after a given amount of time useful for pausing asynchronous programs
Kind: global function
Returns: Promise - promise fulfilled with value
| Param | Type | Description | | --- | --- | --- | | milliseconds | Number | time to pause | | value | * | optional returned value |
Example
import {pause} from "async-endopint.js";
const main = async ()=>{
console.log("hello");
await pause(1000);
console.log("goodbye");
}
main();
//logs "hello"
//logs "goodbye" (after 1 second)
composeAsyncTransformer(last, first) ⇒ AsyncTransformer
composes two asynchoronous transformers
Kind: global function
Returns: AsyncTransformer - asynchonous compositon of current and pre
| Param | Type | Description | | --- | --- | --- | | last | AsyncTransformer | transformer to apply last | | first | AsyncTransformer | transformer to apply first |
Example
import {composeAsyncTransformer} from "async-endopint.js";
const t1 = async (x)=>`<${x}>`;
const t2 = async (x)=>`[${x}]`;
const t = composeAsyncTransformer(t1, t2);
t("hello").then(console.log);
//logs "<[hello]>"
createQueue(...initial) ⇒ PushPair
create a queue iterator
Kind: global function
Returns: PushPair - queue and push function
| Param | Type | Description | | --- | --- | --- | | ...initial | * | initial items in queue |
Example
import {createQueue, renderer, renderer as createPassThrough} from "async-endpoint";
import porgram1, program1, program3 from "....js";
const [queue, push] = createQueue();
const passthrough = createPassThrough(push);
passthrough(porgram1(), program2(), program3());
const render = renderer();
render(queue);
createStack(...initial) ⇒ PushPair
create a stack iterator
Kind: global function
Returns: PushPair - stack and push function
| Param | Type | Description | | --- | --- | --- | | ...initial | * | initial items on stack |
Example
import {createStack, renderer, renderer as createPassThrough} from "async-endpoint";
import porgram1, program1, program3 from "....js";
const [stack, push] = createStack();
const passthrough = createPassThrough(push);
passthrough(porgram1(), program2(), program3());
const render = renderer();
render(stack);
createProgramQueue() ⇒ PushPair
identity program that outputs what ever is input Like "queue", but accepts program as input
Kind: global function
Returns: PushPair - iterator and push function
Example
import {createProgramQueue, renderer} from "async-endpoint";
import porgram1, program1, program3 from "....js";
const [queue, push] = createProgramQueue();
push(porgram1(), program2(), program3());
const render = renderer();
render(queue);
createProgramStack() ⇒ PushPair
identity program that outputs what ever is input Like "queue", but accepts program as input
Kind: global function
Returns: PushPair - iterator and push function
Example
import {createProgramStack, renderer} from "async-endpoint";
import porgram1, program1, program3 from "....js";
const [stack, push] = createProgramStack();
push(porgram1(), program2(), program3());
const render = renderer();
render(stack);
take(iterator, num, [skip]) ⇒ Promise.<Array>
extract items from iterator as array
Kind: global function
Returns: Promise.<Array> - stack and push function
| Param | Type | Default | Description | | --- | --- | --- | --- | | iterator | AsynchronousIterator | | iterator from which to take | | num | Number | | number of items to take from iterator | | [skip] | Number | 0 | number of items to skip before taking |
Example
import {take, continuousOutput} from "async-endpoint";
let i = 0;
take(continuousOutput(()=>i++), 3,1).then(taken=>console.log(taken));
//logs "[1,2,3]"
takeWhile(iterator, accept, [skip]) ⇒ Promise.<Array>
extract first set of items that match a given condition as an array
Kind: global function
Returns: Promise.<Array> - stack and push function
| Param | Type | Default | Description | | --- | --- | --- | --- | | iterator | AsynchronousIterator | | iterator from which to take | | accept | function | | boolean filtering function indicating whether to allow item | | [skip] | Number | 0 | number of items to skip before taking |
Example
import {takeWhile, continuousOutput} from "async-endpoint";
let i = 0;
takeWhile(continuousOutput(()=>i++), x => x < 5, 2).then(taken=>console.log(taken));
//logs "[2,3,4]"
identity([delay], request) ⇒ AsynchronousIterator
program that outputs what ever is put throught
Kind: global function
Returns: AsynchronousIterator - resulting iterator
| Param | Type | Default | Description | | --- | --- | --- | --- | | [delay] | * | 0 | delay between sending output | | request | PairedRequest | | request function for input |
Example
import {identity, renderer, AsyncArray} from "async-endpoint";
const channel = new AsyncArray();
const respond = channel.push.bind(channel),
request = async () => (await channel.next()).value;
identity(undefined, request);
window.respond = respond
continuousOutput([sample]) ⇒ AsynchronousIterator
program that takes no input and contiuously outputs result of calling function
Kind: global function
Returns: AsynchronousIterator - resulting iterator
| Param | Type | Default | Description | | --- | --- | --- | --- | | [sample] | * | ()=>{} | function whose result to output |
Example
import {continuousOutput, renderer} from "async-endpoint";
const render = renderer();
render(continuousOutput(()=>"hello"))
logs "hello" (continously)
...
renderer([...targets]) ⇒ AsyncRenderFunction
creates a render function that renders yeilded results from programs to any number of target functions. If no targets are given, objects will be rendered using "console.log" Can be used as a "passthrough" (see "createQueue" example)
Kind: global function
Returns: AsyncRenderFunction - asychronous render function
| Param | Type | Description | | --- | --- | --- | | [...targets] | function | request function for input |
Example
import {renderer, continuousOutput} from "async-input.js";
const render = renderer();
render(continuousOutput);
//logs "0"
//logs "1"
...
tee(...programs) ⇒ AsyncRenderFunction
creates a render function whos's values are teed on to given It may be advantageous to use this along side a programQueue
Kind: global function
Returns: AsyncRenderFunction - asychronous render function
| Param | Type | Description | | --- | --- | --- | | ...programs | Program | programs to be sent values |
Example
import {tee, continousOutput, renderer} from "async-endpoint";
import porgram1, program1, program3 from "....js";
const instance1 = program1();
const instance2 = program2();
const instance3 = program3();
const render = renderer();
render(instance1, instance2, instance3)
const renderTee = tee(porgram1, program1, program3)
renderTee(continousOutput())
inputConsole(respond)
send input typed into console to a PairedRespond function
Kind: global function
| Param | Type | Description | | --- | --- | --- | | respond | PairedRespond | request function for input |
Example
import {identity, AsyncArray, renderer} from "async-endpoint";
import inputConsole from "async-endpoint/input/console";
const channel = new AsyncArray();
const respond = channel.push.bind(channel),
request = async () => (await channel.next()).value;
const render = renderer();
render(identity(undefined, request))
inputConsole(respond);
inputPipe(respond)
send input piped to console to a PairedRespond function
Kind: global function
| Param | Type | Description | | --- | --- | --- | | respond | PairedRespond | request function for input |
Example
import {identity, renderer, AsyncArray} from "async-endpoint";
import inputPipe from "async-endpoint/input/pipe";
const channel = new AsyncArray();
const respond = channel.push.bind(channel),
request = async () => (await channel.next()).value;
const render = renderer();
render(identity(undefined, request))
inputPipe(respond);
PairedRequest ⇒ Promise.<*>
a function that receives it's response from a paired PairedRespond function
Kind: global typedef
Returns: Promise.<*> - response from respond reunction
PairedRespond : function
a function that sends it's input to a paired PairedRequest function
Kind: global typedef
| Param | Type | Description | | --- | --- | --- | | response | * | response for request function |
AsyncTransformer ⇒ *
stateless asynchronous function that transforms input without side effects
Kind: global typedef
Returns: * - transformed input
| Param | Type | Description | | --- | --- | --- | | input | * | input |
Program ⇒ AsynchronousIterator
an iteractive program
Kind: global typedef
Returns: AsynchronousIterator - asynchronous iterator result
| Param | Type | Description | | --- | --- | --- | | init | * | | | request | PairedRequest | request function for input |
AsyncRenderFunction : function
a function that renders values from a given [Asynchronous] Iterator
Kind: global typedef
| Param | Type | | --- | --- | | program_return; | AsynchronousIterator |
~~PushPair : Array~~
Deprecated
an iterator and a paired function to add to it
Kind: global typedef
Properties
| Name | Type | Description | | --- | --- | --- | | 0 | AsynchornousIterator | iterator | | 1 | function | function used to add to iterator |