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
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
-
AsyncArray β
Array
Functions
-
composePrograms(request, ...programs) β
AsynchornousIterator
-
composes programs sequentially with a single input
-
creates an iterator whose values are mapped from another(iterator, mapper) β
AsynchornousIterator
-
executes a provided funcition for each item of an iterator(iterator, handler) β
undefined
-
filter(iterator, filterer) β
AsynchornousIterator
-
creates an iterator whose values are filtered from another
-
reduce(iterator, reducer, [inital], [condition], [resetInitial]) β
AsynchornousIterator
-
creates an iterator whose values are reduced from another
-
reduceRight(iterator, reducer, [inital], [condition], [resetInitial]) β
AsynchornousIterator
-
creates an iterator whose values are reduced from another
-
pause(milliseconds, value) β
Promise
-
returns a resolved promise after a given amount of time useful for pausing asynchronous programs
-
composeAsyncTransformer(last, first) β
AsyncTransformer
-
composes two asynchoronous transformers
-
createQueue(...initial) β
PushPair
-
create a queue iterator
-
createStack(...initial) β
PushPair
-
create a stack iterator
-
createProgramQueue() β
PushPair
-
identity program that outputs what ever is input Like "queue", but accepts program as input
-
createProgramStack() β
PushPair
-
identity program that outputs what ever is input Like "queue", but accepts program as input
-
take(iterator, num, [skip]) β
Promise.<Array>
-
extract items from iterator as array
-
takeWhile(iterator, accept, [skip]) β
Promise.<Array>
-
extract first set of items that match a given condition as an array
-
identity([delay], request) β
AsynchronousIterator
-
program that outputs what ever is put throught
-
continuousOutput([sample]) β
AsynchronousIterator
-
program that takes no input and contiuously outputs result of calling function
-
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)
-
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
- inputConsole(respond)
-
send input typed into console to a PairedRespond function
- inputPipe(respond)
-
send input piped to console to a PairedRespond function
Typedefs
-
PairedRequest β
Promise.<*>
-
a function that receives it's response from a paired PairedRespond function
-
PairedRespond :
function
-
a function that sends it's input to a paired PairedRequest function
-
AsyncTransformer β
*
-
stateless asynchronous function that transforms input without side effects
-
Program β
AsynchronousIterator
-
an iteractive program
-
AsyncRenderFunction :
function
-
a function that renders values from a given [Asynchronous] Iterator
PushPair :Array
-
an iterator and a paired function to add to it
Array
AsyncArray β 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"
AsynchornousIterator
composePrograms(request, ...programs) β 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;
AsynchornousIterator
creates an iterator whose values are mapped from another(iterator, mapper) β 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"
...
undefined
executes a provided funcition for each item of an iterator(iterator, handler) β 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"
...
AsynchornousIterator
filter(iterator, filterer) β 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"
AsynchornousIterator
reduce(iterator, reducer, [inital], [condition], [resetInitial]) β 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]"
...
AsynchornousIterator
reduceRight(iterator, reducer, [inital], [condition], [resetInitial]) β 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]"
...
Promise
pause(milliseconds, value) β 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)
AsyncTransformer
composeAsyncTransformer(last, first) β 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]>"
PushPair
createQueue(...initial) β 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);
PushPair
createStack(...initial) β 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);
PushPair
createProgramQueue() β 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);
PushPair
createProgramStack() β 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);
Promise.<Array>
take(iterator, num, [skip]) β 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]"
Promise.<Array>
takeWhile(iterator, accept, [skip]) β 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]"
AsynchronousIterator
identity([delay], request) β 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
AsynchronousIterator
continuousOutput([sample]) β 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)
...
AsyncRenderFunction
renderer([...targets]) β 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"
...
AsyncRenderFunction
tee(...programs) β 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);
Promise.<*>
PairedRequest β a function that receives it's response from a paired PairedRespond function
Kind: global typedef
Returns: Promise.<*>
- response from respond reunction
function
PairedRespond : 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 |
AsynchronousIterator
Program β an iteractive program
Kind: global typedef
Returns: AsynchronousIterator
- asynchronous iterator result
Param | Type | Description |
---|---|---|
init | * |
|
request | PairedRequest |
request function for input |
function
AsyncRenderFunction : a function that renders values from a given [Asynchronous] Iterator
Kind: global typedef
Param | Type |
---|---|
program_return; | AsynchronousIterator |
PushPair : Array
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 |