-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
1,831 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,27 @@ | ||
|
||
# Summary | ||
|
||
* [A General Theory of Reactivity](README.md) | ||
* [Introduction](intro.md) | ||
* [Concepts](concepts.md) | ||
* Idioms | ||
- [Iterators](iterators.md) | ||
- [Generator Functions](generator-functions.md) | ||
- [Generators](generators.md) | ||
- [Async Values](async-values.md) | ||
- [Async Functions](async-functions.md) | ||
- [Async Queues](async-queues.md) | ||
- [Semaphores](semaphores.md) | ||
- [Async Buffers](async-buffers.md) | ||
- [Async Iterators](async-iterators.md) | ||
- [Remote Iterators](remote-iterators.md) | ||
- [Async Generators](async-generators.md) | ||
- [Async Generator Functions](async-generator-functions.md) | ||
- [Observables](observables.md) | ||
- [Observables and Signals](signals.md) | ||
- [Behaviors](behaviors.md) | ||
* Concrete Cases | ||
- [Progress and ETA](progress.md) | ||
* [Conclusion](conclusion.md) | ||
* [Glossary](glossary.md) | ||
* [Acknowledgements](acknowledgements.md) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
# Acknowledgements | ||
|
||
I am grateful to Domenic Denicola, Ryan Paul, and Kevin Smith for reviewing and | ||
providing feedback on various drafts of this article. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
|
||
# Promise Buffers | ||
|
||
Consider another application. | ||
You have a producer and a consumer, both doing work asynchronously, the producer | ||
periodically sending a value to the consumer. | ||
To ensure that the producer does not produce faster than the consumer can | ||
consume, we put an object between them that regulates their flow rate: a buffer. | ||
The buffer uses a promise queue to transport values from the producer to the | ||
consumer, and another promise queue to communicate that the consumer is ready | ||
for another value from the producer. | ||
The following is a sketch to that effect. | ||
|
||
```js | ||
var outbound = new PromiseQueue(); | ||
var inbound = new PromiseQueue(); | ||
var buffer = { | ||
out: { | ||
next: function (value) { | ||
outbound.put({ | ||
value: value, | ||
done: false | ||
}); | ||
return inbound.get(); | ||
}, | ||
return: function (value) { | ||
outbound.put({ | ||
value: value, | ||
done: true | ||
}) | ||
return inbound.get(); | ||
}, | ||
throw: function (error) { | ||
outbound.put(Promise.throw(error)); | ||
return inbound.get(); | ||
} | ||
}, | ||
in: { | ||
yield: function (value) { | ||
inbound.put({ | ||
value: value, | ||
done: false | ||
}) | ||
return outbound.get(); | ||
}, | ||
return: function (value) { | ||
inbound.put({ | ||
value: value, | ||
done: true | ||
}) | ||
return outbound.get(); | ||
}, | ||
throw: function (error) { | ||
inbound.put(Promise.throw(error)); | ||
return outbound.get(); | ||
} | ||
} | ||
}; | ||
``` | ||
|
||
This sketch uses the vernacular of iterators and generators, but each of these | ||
has equivalent nomenclature in the world of streams. | ||
|
||
- `in.yield` means “write”. | ||
- `in.return` means “close”. | ||
- `in.throw` means “terminate prematurely with an error”. | ||
- `out.next` means “read”. | ||
- `out.throw` means “abort or cancel with an error”. | ||
- `out.return` means “abort or cancel prematurely but without an error”. | ||
|
||
So a buffer fits in the realm of reactive interfaces. | ||
A buffer has an asynchronous iterator, which serves as the getter side. | ||
It also has an asynchronous generator, which serves as the setter dual. | ||
The buffer itself is akin to an asynchronous, plural value. | ||
In addition to satisfying the requirements needed just to satisfy the | ||
triangulation between synchronous iterables and asynchronous promises, | ||
it solves the very real world need for streams that support pressure | ||
to regulate the rate of flow and avoid over-commitment. | ||
An asynchronous iterator is a readable stream. | ||
An asynchronous generator is a writable stream. | ||
|
||
| Stream | | | | | ||
| ----------------- | ------- | -------- | ------------ | | ||
| Promise Buffer | Value | Plural | Temporal | | ||
| Promise Iterator | Getter | Plural | Temporal | | ||
| Promise Generator | Setter | Plural | Temporal | | ||
|
||
A buffer has a reader and writer, but there are implementations of reader and | ||
writer that interface with the outside world, mostly files and sockets. | ||
|
||
In the particular case of an object stream, if we treat `yield` and `next` as | ||
synonyms, the input and output implementations are perfectly symmetric. | ||
This allows a single constructor to serve as both reader and writer. | ||
Also, standard promises use the [Revealing Constructor] pattern, exposing the | ||
constructor for the getter side. | ||
The standard hides the `Promise.defer()` constructor method behind the scenes, | ||
only exposing the `resolver` as arguments to a setup function, and never | ||
revealing the `{promise, resolver}` deferred object at all. | ||
Similarly, we can hide the promise buffer constructor and reveal the input side | ||
of a stream only as arguments to the output stream constructor. | ||
|
||
```js | ||
var reader = new Stream(function (write, close, abort) { | ||
// ... | ||
}); | ||
``` | ||
|
||
The analogous method to `Promise.defer()` might be `Stream.buffer()`, which | ||
would return an `{in, out}` pair of entangled streams. | ||
|
||
[Revealing Constructor]: http://domenic.me/2014/02/13/the-revealing-constructor-pattern/ | ||
|
||
See the accompanying sketch of a [stream][] implementation. | ||
|
||
[stream]: http://kriskowal.github.io/gtor/docs/stream | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
|
||
# Asynchronous Functions | ||
|
||
Generator functions have existed in other languages, like Python, for quite some | ||
time, so folks have made some clever uses of them. | ||
We can combine promises and generator functions to emulate asynchronous | ||
functions. | ||
The key insight is a single, concise method that decorates a generator, creating | ||
an internal "promise trampoline". | ||
An asynchronous function returns a promise for the eventual return value, or the | ||
eventual thrown error, of the generator function. | ||
However, the function may yield promises to wait for intermediate values on its | ||
way to completion. | ||
The trampoline takes advantage of the ability of an iterator to send a value | ||
from `next` to `yield`. | ||
|
||
```js | ||
var authenticated = Promise.async(function *() { | ||
var username = yield getUsernameFromConsole(); | ||
var user = getUserFromDatabase(username); | ||
var password = getPasswordFromConsole(); | ||
[user, password] = yield Promise.all([user, password]); | ||
if (hash(password) !== user.passwordHash) { | ||
throw new Error("Can't authenticate because the password is invalid"); | ||
} | ||
}) | ||
``` | ||
|
||
Mark Miller’s [implementation][Async] of the `async` decorator is succinct and | ||
insightful. | ||
We produce a wrapped function that will create a promise generator and proceed | ||
immediately. | ||
Each requested iteration has three possible outcomes: yield, return, or throw. | ||
Yield waits for the given promise and resumes the generator with the eventual | ||
value. | ||
Return stops the trampoline and returns the value, all the way out to the | ||
promise returned by the async function. | ||
If you yield a promise that eventually throws an error, the async function | ||
resumes the generator with that error, giving it a chance to recover. | ||
|
||
[Async]: http://wiki.ecmascript.org/doku.php?id=strawman:async_functions#reference_implementation | ||
|
||
```js | ||
Promise.async = function async(generate) { | ||
return function () { | ||
function resume(verb, argument) { | ||
var result; | ||
try { | ||
result = generator[verb](argument); | ||
} catch (exception) { | ||
return Promise.throw(exception); | ||
} | ||
if (result.done) { | ||
return result.value; | ||
} else { | ||
return Promise.return(result.value).then(donext, dothrow); | ||
} | ||
} | ||
var generator = generate.apply(this, arguments); | ||
var donext = resume.bind(this, "next"); | ||
var dothrow = resume.bind(this, "throw"); | ||
return donext(); | ||
}; | ||
} | ||
``` | ||
|
||
As much as JavaScript legitimizes the async promise generators by supporting | ||
returning and throwing, now that Promises are part of the standard, the powers | ||
that sit on the ECMAScript standards committee are contemplating special `async` | ||
and `await` syntax for this case. | ||
The syntax is inspired by the same feature of C#. | ||
|
||
```js | ||
var authenticate = async function () { | ||
var username = await getUsernameFromConsole(); | ||
var user = getUserFromDatabase(username); | ||
var password = getPasswordFromConsole(); | ||
[user, password] = await Promise.all([user, password]); | ||
return hash(password) === user.passwordHash; | ||
}) | ||
``` | ||
|
||
One compelling reason to support special syntax is that `await` may have higher | ||
precedence than the `yield` keyword. | ||
|
||
```js | ||
async function addPromises(a, b) { | ||
return await a + await b; | ||
} | ||
``` | ||
|
||
By decoupling **async functions** from **generator functions**, JavaScript opens | ||
the door for **async generator functions**, foreshadowing a **plural** and | ||
**temporal** getter, a standard form for readable streams. | ||
|
Oops, something went wrong.